go+mysqlでつくるREST API
APIの名前部分が脆弱な文字列ベースのSQLインジェクション
- route
a.Router.HandleFunc("/horse/{search}", a.searchHorse).Methods("GET")
- SQL
statement := fmt.Sprintf("SELECT `id` FROM `horses` WHERE `name`='%s'", search)
rows, err := db.Query(statement)
statement := "SELECT `id` FROM `horses` WHERE `name`=?"
rows, err := db.Query(statement, search)
出力をただエスケープしてない
http://192.168.5.4:8083/ にアクセスしてPOSTにhtmlタグを
package main
import "fmt"
import "net/http"
func main() {
//ハンドラの登録
http.HandleFunc("/", handler)// /というパターンのパスで来たリクエストをハンドリングする関数を登録
http.ListenAndServe(":8083", nil)//nilだとhttp.DefaultServeMux
}
//ハンドラ
func handler(
//レスポンスを書き込む
w http.ResponseWriter,
//リクエスト
r *http.Request) {
//入力された文字列
value_xss := r.FormValue("xss")
form:=`<html>
<form method="post" action="">
<input type="text" size="60" name="xss" value="`+value_xss+`">
<input type="submit" value="post">
</form>
</html>
`
fmt.Fprint(w, form)
}
func noXSShandler(
//レスポンスを書き込む
w http.ResponseWriter,
//リクエスト
r *http.Request) {
//入力された文字列
value_xss := r.FormValue("xss")
form:=`<html>
<form method="post" action="">
<input type="text" size="60" name="xss" value="`+html.EscapeString(value_xss)+`">
<input type="submit" value="post">
</form>
</html>
`
fmt.Fprint(w, form)
}
file=で内部のファイルを読むで http.FileServer()を使う http.ServeFile()を使うと割と安全だと思う http.Handle()では../../../みたいなのが入ると301になる
http://192.168.5.4:8082/?file=/etc/hosts
package main
import (
"fmt"
"net/http"
"html"
"os"
"bufio"
)
func main() {
//ハンドラの登録
http.HandleFunc("/", handler)// /というパターンのパスで来たリクエストをハンドリングする関数を登録
http.ListenAndServe(":8084", nil)//nilだとhttp.DefaultServeMux
}
//ハンドラ
func handler(
//レスポンスを書き込む
w http.ResponseWriter,
//リクエスト
r *http.Request) {
//入力された文字列
value_file := r.FormValue("file")
form1:=`<html>
<form method="get" action="">
<input type="text" size="60" name="file" value="`+html.EscapeString(value_file)+`">
<input type="submit" value="post">
</form><textarea>`
form2:=`</textarea>
</html>
`
fmt.Fprint(w, form1)
if value_file!=""{
fp, err := os.Open(value_file)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
fmt.Fprint(w,scanner.Text())
}
fp.Close()
}
fmt.Fprint(w, form2)
}
package main
import (
"net/http"
"strings"
)
func Handler( w http.ResponseWriter, r *http.Request ) {
if strings.HasPrefix(r.URL.Path, "/static/") {
http.ServeFile(w, r, r.URL.Path[1:])
} else {
http.NotFound(w, r)
}
}
func main() {
http.HandleFunc("/", Handler)
http.ListenAndServe(":8082", nil)
}
"typ":"none"の場合、改ざん検証がないので書き換え可能 https://blog.motikan2010.com/entry/2017/05/12/jwt-go%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B {"alg":"HS256","typ":"JWT"} を {"alg":"none","typ":"JWT} に変更できるパターンもあるらしい(golangでは無理)
- http://192.168.5.4:8084/api/ にアクセスするとトークンが返ってくる
- http://192.168.5.4:8084/api/private/ にアクセスすると入力エラー
- 2.のリクエストヘッダに Authorization: {1.で取得したトークン} を付けて再送
$ curl -v http://192.168.5.4:8084/api/private/ -H "Authorization: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOjE1MzkzMjcxNzIsInVzZXIiOiJndWVzdCJ9."
ログインできて
{"message":"こんにちは、「 guest 」さん"}
が返る 4. トークンの後ろ半分をbase64デコードすると{"exp":1539327172,"user":"guest"}になる。 トークンの後ろ半分を、{"exp":1539327172,"user":"admin"}に変更してbase64エンコードした値に入れ替えて再送すると
$ curl -v http://192.168.5.4:8084/api/private/ -H "Authorization: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOjE1MzkzMjcxNzIsInVzZXIiOiJhZG1pbiJ9."
{"message":"こんにちは、「 admin 」さん"}
が返る
ローカルIPアドレス(LAN内)の外からアクセスできないはずのコンテンツが読める 標準のhttp.Get とかではhttp/https以外のスキームはフォローしてなさげ URLの内容とかはburp collaboratorを使うとよさそう(goだと基本的にURLエンコードされる)
1.http://192.168.5.4:8085/?url=http%3A%2F%2F内部IPアドレス%2F 2.http://192.168.5.4:8085/img のデータは
func handler(
//レスポンスを書き込む
w http.ResponseWriter,
//リクエスト
r *http.Request) {
//入力された文字列
url := r.FormValue("url")
if url !=""{
response, err := http.Get(url)
//基本URLエンコードされる
if err != nil {
panic(err)
}
defer response.Body.Close()
file, err := os.Create("save.jpg")
if err != nil {
panic(err)
}
defer file.Close()
io.Copy(file, response.Body)
}
}
脆弱なコードがわりと無茶なので
exec.Commandは渡せなさげだけど exec.Command("bash", "-c", "ping -c 5 "+host)みたいなことをやってしまうとできる
http://192.168.5.4:8086/rce?rce=bogus.jp%3Bls http://192.168.5.4:8086/rce?rce=bogus.jp|ls
if value_rce!=""{
fmt.Fprint(w,"処理開始: ", time.Now().Format("15:04:05"))
host :=html.EscapeString(value_rce)
out, err := exec.Command("bash", "-c", "ping -c 5 "+host).CombinedOutput()
if err != nil {
fmt.Fprint(w,"Command Exec Error.")
}
fmt.Fprint(w,"\nping終了: ", string(out))
}
if value_rce!=""{
fmt.Fprint(w,"処理開始: ", time.Now().Format("15:04:05"))
host :=html.EscapeString(value_rce)
out, err := exec.Command("ping", "-c 5", host).CombinedOutput()
if err != nil {
fmt.Fprint(w,"Command Exec Error.")
}
fmt.Fprint(w,"\nping終了: ", string(out))
}
http://192.168.5.4:8086/rce?rce=bogus.jp|sleep%2010
301ヘッダを書いてw.WriteHeader()するか http.Redirect()するところ
%0dと%0aはスペースに変換される
http://192.168.5.4:8087/rd?redirect=http://bogus.jp http://192.168.5.4:8087/hrd?redirect=http://bogus.jp
package main
import (
"net/http"
)
func headerRedirectHandler(w http.ResponseWriter, r *http.Request) {
value_redirect := r.FormValue("redirect")
if value_redirect!=""{
w.Header().Set("Content-Type", "text/html")
w.Header().Set("location", value_redirect)
w.WriteHeader(http.StatusMovedPermanently) // 301 Moved Permanently
}
}
func RedirectHandler(w http.ResponseWriter, r *http.Request) {
value_redirect := r.FormValue("redirect")
if value_redirect!=""{
http.Redirect(w, r, value_redirect, 301)
}
}
func main(){
http.HandleFunc("/hrd", headerRedirectHandler)
http.HandleFunc("/rd", RedirectHandler)
http.ListenAndServe(":8087", nil)
}
バリデーションするしかない?
XSSで
原理的に難しそうってか普通にgopathの設定うまくやらないとローカルのファイルインクルードできないんですけど
xml.Unmarshal()は外部実体参照よまなさげ(xxe01)