之前在学校的时候参加了ctf比赛,有幸拿了一个代码审计题的一血,但是当时可能也是算运气比较好吧,因为我的思路并不是常规解法,但还是拿了一血,现在更深入的学习php和sql注入之后,反过来看这道代码审计题,又有一种豁然开朗的感觉。虽然一度到了第二名,但是最后还是没有稳住,不过在我们队伍前面的都是大三的学长,这也不算特别难受吧。
初步思路
这个题目是个源码泄露的题,用WWW.zip就能够将他的代码下载到本地,如下:
拿到之后以当时的技术水平肯定是审计不出来的,所以直接上了sexy代码审计系统审计:
审计结果如图,首先我排除了3,因为xss漏洞必须要有道管理员点击之后才会生效,所以直接放弃,那么突破这道题目的关键就放到前面两个点上,找到核心的functions.php
代码如下:
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
| <?php error_reporting(0); session_start(); include("config_ini.php");
function escape1($string){ $a = 1; $b = 2; global $link; $string = $link->real_escape_string($string); return $string; }
function check($seesee){ $seesee = trim(escape1($seesee)); if(strlen($seesee)<5){ die("你谁啊你,你就想登录"); } if(strlen($seesee)>11){ $seesee = substr($seesee, 0, 11); } return $seesee; }
function Drawtarget($file,$path){ $zipfile = new ZipArchive; if ($zipfile->open($file) === TRUE) { $zipfile->extractTo($path); $zipfile->close(); } } function SeeseeDir($path) { $papers = scandir($path); foreach ($papers as $document) {14 $address = "$path/$document"; if (is_file($address)) { $section = pathinfo($document); $tailoring = strtolower($section['extension']); $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","pHp","pHp5","pHp4","pHp3","pHp2","Html","Htm","pHtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","jSp","jSpx","jSpa","jSw","jSv","jSpf","jHtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","aSp","aSpx","aSa","aSax","aScx","aShx","aSmx","cEr","sWf","swf","htaccess","ini"); if (!in_array($tailoring, $deny_ext)) { @chmod($address, 0666); } else { @chmod($address, 0666); unlink($address); } } elseif ($document != '.' && $document != '..' && is_dir($address)) { CheckDir($address); } } }
function check_lg(){ if($_SESSION['logged']!=1){ die("请先登录"); } } function check_ad(){ if($_SESSION['admin']!="True"){ die("啊,只有管理员才可以哦!"); } } function send_to_phone($randomssss){ $phone_number="1111111111"; } ?>
|
管理员后台login.php
的代码如下:
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
| <?php
include("config_ini.php"); include("functions_ini.php");
session_start();
$user = $_POST['username']; $pass = $_POST['password'];
$user = check($user); $pass = check($pass);
$sql = "SELECT username, password FROM ctf2020 WHERE username='" .$user ."' && password='" .$pass ."'"; $res = $link->query($sql);
if ($res->num_rows > 0 or $_SESSION['logged']==1){ $_SESSION['logged'] = 1; header("Location: admin.php"); } else{ echo "小老弟,就这还谈恋爱?"."<br>"; }
$link->close();
?>
|
这段代码在当时的我看来是一头雾水,一开始用bp去爆破管理员登录后台无果
然后我的直觉就是sql注入进管理员后台,于是跟着saxy审计出来的php去找sql漏洞,remote_admin.php
代码如下:
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
| <?php
include "functions_ini.php"; session_start();
check_lg(); error_reporting(0);
$remote_check_admin = create_function("",'if(isset($_SERVER["HTTP_CHECK_ADMIN"])){$_SERVER["REMOTE_ADDR"] = $_SERVER["HTTP_CHECK_ADMIN"];}');
mt_srand(time()); $rdm = mt_rand(); $rdmrd = $rdm.time();
eval("function admin_$rdmrd() {" ."global \$remote_check_admin; \$remote_check_admin();" ."}");
send_to_phone($rdmrd);
$_GET['rdmrd'](); if($_SERVER['REMOTE_ADDR']=="127.0.0.1"){ $_SESSION['admin'] = "True"; echo "欢迎您admin,即将为您跳转到后台"; sleep(3); echo "<script> alert('欢迎您admin,即将为您跳转到后台');parent.location.href='./admin.php'; </script>"; } ?>
|
找到eval("function admin_$rdmrd()
这个地方对应上去找到的是create_function()
这样一个匿名函数
然后百度大法发现这个地方果真有所谓的后门漏洞:
但是很遗憾,这个地方即使有匿名函数这个漏洞,但是还是不能够通过这个地方实行sql注入,原因就在functions_ini.php的这个地方:
这个地方他使用了一个real_escape_string()的匿名函数,这个匿名函数可不得了,这个函数一出,可以说几乎断绝了sql注入的一切可能,他的功能简单一点说就是在所有的可注入符号后面加一个转义符(\),也就是如果你用id=1’进行注入,他会自动在id=1’后加一个转义符,即变成id=1‘\,也就是说’会被转义掉,就不能进行sql注入。
突破点
这个地方在经过了这么久的学习之后,如果这个地方是采用的gbk编码,那么宽字节注入应该也是行得通的。这个地方其实当时是卡了很久的,甚至一度放弃了sql注入的想法,但是在后面仔细对代码一个函数一个函数的查功能之后,终于发现了一个让我兴奋的点,
real_escape_string()
和substr()
这两个函数一起使用,那么就存在了一个问题:
substr()
函数返回字符串的一部分
如:echo substr(“Hello world”,10),返回的值是d
echo substr(“Hello world”,2),返回的值就是llo world
那么再看一下这段代码:
这里用substr()
截取seesee
这个变量,因为如果在字符串的后面加上任意一个符号,real_escape_string()
都是会将它加上一个转义符的,但是这里substr(\$seesee,0,11)
只截取0-11取11位,那么如果我构造一个“aaaaaaaaaa\
”这个字符串,通过real_escape_string()
这个函数转义之后就会变成12位:
“aaaaaaaaaa\\
” ,但是substr()
这个函数只取11位,那么 \ 就会跟着传进sql语句中执行,达到绕过real_escape_string()
的效果,构造密码为”or+2>1#
“,这时候的sql语句就应该为:
1 2
| SELECT username, password FROM ctf2020 WHERE username='aaaaaaaaaa\' && password='or+2>1#'
|
进到登录页面输入帐号和密码
ok第一关已经被攻破,进入了他的后台
绕过管理员权限
进入了后台,但是这个地方显示要管理员才能够继续操作,一开始想的是利用bp构造一个127.0.0.1的本地xff,然而无果,又只有审计代码,还是回到了最先找到漏洞的那个地方
这个地方因为create_function()
这个函数的漏洞,能够构造一个匿名函数来伪造REMOTE_ADDR
科普一下REMOTE_ADDR
:
1
| 是你的客户端跟你的服务器“握手”时候的IP。如果使用了“匿名代理”,REMOTE_ADDR将显示代理服务器的IP
|
碰巧看到一个相似的ctf题目如下:
构造一个\x00lambda
的匿名函数通过bp爆破进入管理员后台
bp抓包lambda函数的变量,因为是本地管理员登录,加上一个check-admin:127.0.0.1
这里我写了一个1-1000的字典对其进行爆破,只要返回值为200即响应成功
关掉bp的抓包刷新页面后如图所示:
文件上传绕过黑名单
这个地方第二关也已经绕过了,进入了他管理员的界面,接下来就是文件上传了,但是这个文件上传很贼,先看一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function SeeseeDir($path) { $papers = scandir($path); foreach ($papers as $document) {14 $address = "$path/$document"; if (is_file($address)) { $section = pathinfo($document); $tailoring = strtolower($section['extension']); $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","pHp","pHp5","pHp4","pHp3","pHp2","Html","Htm","pHtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","jSp","jSpx","jSpa","jSw","jSv","jSpf","jHtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","aSp","aSpx","aSa","aSax","aScx","aShx","aSmx","cEr","sWf","swf","htaccess","ini"); if (!in_array($tailoring, $deny_ext)) { @chmod($address, 0666); } else { @chmod($address, 0666); unlink($address); } } elseif ($document != '.' && $document != '..' && is_dir($address)) { CheckDir($address); } }
|
重点看一下$deny_ext这一行,这一行代码就是限制上传的文件类型,可以说其实已经做得很完美了,无论是大小写的文件都已经被过滤掉了,不过出题人还是忘过滤了一个文件类型,就是php7(php是世界上最好的语言,QAQ)
那么直接构造一个php7的一句话木马,压缩成zip
试着上传一下,果然没有出错,返回了文件上传的路径
直接访问这个路径,然后hackbar post传参,直接拿到flag
当时有几个地方其实是靠学长的提示才能够继续往下进行,但是总体来说的思路还是没有错的,经过一段时间的学习之后,现在回过头来看这些php代码又有了一种豁然开朗的感觉