ひよこになりたい

Programming Server Network Security and so on

Python+Selenium+Phantom.js+Beautifulsoupでスクレイピングする

※【2018/04/17追記】

Phantom.jsはメンテナンスが終了したようです。今後はGoogle Chromeを使用してJavascriptの処理を行っていくことになります。以下の記事で解説していますので合わせてご覧ください。 zipsan.hatenablog.jp

【追記終わり】

最近スクレイピングスクリプト書いて遊んでいるのでそれについてのメモがてらに。

Pythonスクレイピングする方法は多々あるみたいなんですが,個人的に一番使いやすかった(慣れ?)のがこの組み合わせでした。

以前Pythonのurllib.request+Beautifulsoupでレスポンスhtmlの解析をして次々とたどっていくようなスクリプトを書いていたんですが、これだとJavascriptで追加されたエレメントは受け取れなかったり、リダイレクト処理がめっちゃ大変だったり色々と面倒でしたが今回SeleniumとPhantomjsを使用することでその辺りの面倒な処理を一括でできるようになりました。

簡単に流れを説明すると、PythonSeleniumを操作し、SeleniumがPhantom.jsでJSを実行し、結果のHTMLをBeautifulSoupでパースし、解析していきます。

Selenium

Seleniumはブラウザの自動化を行うツールです。複数のブラウザでWebのテストを実行したりすることができたり、Android/iOSでテスト出来たりいろいろと便利(Seleniumサーバー建てて集中管理したりもできるみたいだし)。今回はFirefoxChromeの代わりにPhantom.jsを使います。
Selenium - Web Browser Automation

Phantom.js

Phantom.jsは本来はブラウザがないと実行できないJavascriptを、ブラウザ画面なしで実行できるすごいやつ。API形式で叩けるっぽい? PhantomJS | PhantomJS

Beautiful soup

Beautiful soupはHTML/XMLのパーサーで、HTMLを解析して使いやすくしてくれるものです。HTMLをDOMに倣って列挙したり検索したり選択したりできます。
Beautiful Soup: We called him Tortoise because he taught us.

環境設定とか

特にそんなにすることないけど・・・
使用した言語はPython3.4です。Linux, Mac, Windowsで動くのを確認

Python3は入ってること前提で。まずはpythonseleniumモジュールのインストール

pip3 install selenium

次にPhantom.jsを入れます。これは特に説明しないので適当に入れてください。 http://phantomjs.org/
ちゃんとパスを通しておくこと。

最後にBeautifulSoupを入れます。bsはpython3の場合は2to3コマンドでpython3用に変換する必要があります(公式でそう書いてある)。
http://www.crummy.com/software/BeautifulSoup/#DownloadここからBeautiful Soup 4を落としてきて2to3で変換。

wget http://www.crummy.com/software/BeautifulSoup/bs4/download/4.3/beautifulsoup4-4.3.2.tar.gz  # 現時点(2015/04/12)での最新版
tar zxf beautifulsoup4-4.3.2.tar.gz
cd ./beautifulsoup4-4.3.2
2to3 -w bs4
python3 setup.py

うまく行かなければ2to3した後に直接ライブラリディレクトリの中に突っ込んでもOK

使ってみる

from selenium import webdriver
from bs4 import BeautifulSoup

driver = webdriver.PhantomJS()
driver.get("http://sukumizu.moe/")
data = driver.page_source.encode('utf-8')

print(data)
driver.save_screenshot("ss.png")

driver.quit()

結果

b'<!DOCTYPE html><html><head>\n\t<title>sukumizu.moe</title>\n\t<link rel="st(略

こんなかんじで扱えます。
スクリーンショットも撮れます。

UAを指定したい場合はこんな感じ。以下はChromeの例

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

des_cap = dict(DesiredCapabilities.PHANTOMJS)
des_cap["phantomjs.page.settings.userAgent"] = (
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
    'Chrome/28.0.1500.52 Safari/537.36'
)
driver = webdriver.PhantomJS(desired_capabilities=des_cap)
driver.get("http://sukumizu.moe/")
data = driver.page_source.encode('utf-8')

取得したhtmlの解析 (Beautiful soup)

from bs4 import BeautifulSoup

# ----- 略 -----

html = BeautifulSoup(data)
print(html)  # htmlソースを表示する

print(html.title)  # タイトルタグ

print(html.title.string)  # タイトルタグ内の文字

print(html.find('h1'))  # h1タグ

print(html.find_all('link'))  # 全てのlinkタグのリスト

print(html.find_all('link', attrs={'href': 'style.css'}))  # linkタグかつhrefがstyle.cssのもののリスト

結果

<!DOCTYPE html>
<html><head>
<title>sukumizu.moe</title>
---(略)---
</body></html>

<title>sukumizu.moe</title>

sukumizu.moe

<h1>What is your favorite "sukumizu"...? </h1>

[<link ...., <link ...., <link ....]

[<link href="style.css" rel="stylesheet"></link>]

これくらい使えれば困らないかも。他にもいろいろあるのでbsのドキュメントを参照。

タグの要素の選択やチェックはブラウザ標準の「要素を検証」「開発ツール」が便利。 Chromeなら左上の虫眼鏡、Firefoxなら右上の矢印で要素の選択ができます

pythonは簡単でいいなあ

セキュリティ・キャンプ九州 in 福岡 2014 Writeup

セキュリティ・キャンプ九州 in 福岡 2014 (SecurityCamp Kyushu 2014)に参加してきました。

セキュキャンと言えば、選抜されたメンバーが情報セキュリティに特化した様々な技術を学んぶ合宿です。近年の日本の情報セキュリティの需要増加や技術者不足を解消するために、様々なセキュリティ系の企業やIT系の企業、また独立行政法人や官公庁などの支援により毎年開催されています。

 

セキュキャン全国大会の方は22歳以下の学部生以下が対象で、院生は参加不可能なのですが、今回はなんと九州大会に限り院生参加可能!ということですぐに応募用紙を書き、運良く選考に受かることが出来ました。

 

セキュキャン九州の日程は2014年8月29日~31日。夏休み最後の日に開催でした。

 

実はその周辺、大学のコース授業で1週間ほど大阪で合宿→その後研究室合宿で3日ほど大分へ合宿→1日休みの後セキュリティ・キャンプ→セキュキャン終了翌日神戸で合宿という、普段家から出ない学生にはかなりしんどい日程でした。(これ書いてる途中も神戸合宿の間只中です)

 

しかし、そんなハードスケジュールでも参加してよかった。とても充実したキャンプでした。

日程

8月29日 金曜日(1日目)

9:30  受付開始

10:00 オープニング

11:00 『情報セキュリティ技術の使い方~技術を学んで怪物となる前に』 吉井講師

12:00 『CTF、バグハント、コンテストの現在。~セキュリティ・キャンプの紹介も兼ねて~』園田講師

13:00 お昼休み

14:00 『怪しいアプリ、怪しくないアプリ』 宮本講師、園田講師

16:00 『CTFを通じて学ぶセキュリティ技術基礎』続き 花田講師

17:00 ホテル移動、夕食

19:00 『セキュリティ技術者の職種ガイド ~セキュリティ対応ケーススタディ と絡めて~』 堂領講師

21:00 終了

8月30日 土曜日(2日目)合宿講習

9:00  『プログラミングとセキュリティ』 小出講師

12:00 お昼休み

13:00 『Webセキュリティ基礎&実践』 服部講師

17:00 ホテル移動、夕食

19:00 『情報セキュリティ技術の使い方をケースで考えよう』 吉井講師

21:00 終了

8月31日 日曜日(3日目)合宿講習

9:00  『ハニーポット+簡易セキュリティ診断講座+マルウェア解析講座(前編)』 濱本講師

12:00 お昼休み

13:00 『ハニーポット+簡易セキュリティ診断講座+マルウェア解析講座(後編)』 濱本講師

15:00 クロージング

16:00 案内

16:30 終了

一日目

一日目は9:30~10:00集合。福岡に住んでるお陰でそんなに早い時間に出発しなくても大丈夫でした。他の県の方々は夜行バスだったり新幹線だったりなかなか大変だったみたい。

@mrtc0氏も参加するということで、非常に楽しみ。

 

場所はエルガーラホール。会場に到着するともうすでに結構な人数が到着していました。 

小出先生(@hirosk)や、県警の方などと少しお話をし、適当に開いてる席に座り、MBAを開いて適当にツイッターしていると

 

隣が@mrtc0氏でした。すごい偶然。ということで名刺交換をしました。

@mrtc0氏・・・怖い人だ

 

 

講演の内容は三輪会長による技術者の人材に関することであったり、吉井先生による法律の問題であったり、園田さんによるCTFに関するお話であったり、情報セキュリティの表面に現れる問題に関する内容でした。特記はしませんが、非常に楽しめる内容でした。

 

昼食後、15:00(16:00?)くらいから別の館に移動。西日本新聞社さんの会議室の模様。

  

その後、花田さん(@decoy_service)による簡易CTF大会が行われました。練習問題を5問ほど解き、すぐにCTF大会の方に移行。

問題的にはFor(+NW)とCry系が大半でした。200点までの問題は基本的なこと(fileやstiringsコマンド、基本ツールの使用)が殆どで、それ以上の点数の問題は海外のCTF(CSAW, backdoorなど)の300,400点問題となっていて、難易度が非常に高かったように感じました。また、独自問題も有り、そのほとんどが発想力を問われるものが多く、非常につらかったです。

 

最終的には3位と同点の4位でした。エスパー系の問題が全然解けなかった。

 

CTF終了後、夕食タイム。夕食はホテルの食事でした。席につき食事を待っていたのですが・・・

皆無言でお通夜状態でした w

 

せっかくのキャンプで無言は嫌なので、ちょこちょこ話を振っていたのですがなかなか会話が弾まず。。(でもご飯美味しかった)

  

無言の夕食を経て、堂領講師による情報セキュリティの職種に関するお話がありました。ここでは情報セキュリティの職種にはどのようなものがあるのか具体的に十数個例を挙げて解説をしていただきました。分類や就労場所など細かな要素までおしえていただいたので非常に参考になりました。

 

ここで一日目が終了。 

2日目

朝7時に朝食だったのでめちゃくちゃつらかったです。前日の無言感は皆もやばいと思ってたようで

えすらん氏(@nrsdogs)が話題を作ってくれてました(よかった) 

 

 

朝食後、2日目のはじめの講義。うちのボスの小出先生(@hirosk)の「プログラミングとセキュリティ」でした。JavaGlassFishでWebアプリを作成し、プログラミング上でどのようなセキュリティの考え方やコーディングがあるのかの解説でしたが、不慮の事故でセキュリティまで届かず。終了後にサイボウズLive上でアフターケアを行ってくれました。ハンズオンは環境が違うとプロでも難しいみたい

 

前日、班員の皆さんとワイワイCTFをやっていたので若干寝不足だったのですが、エンジニアの主食のお陰で生きることが出来ました。↓

午後は「Webセキュリティ基礎&実践」と題して服部先生からのCTF大会Webアプリケーションのセキュリティについてでした。

Webセキュリティとは何だったのか。(いやセキュリティだけど)

 

いきなりXSSの実習を行いました。level1~5までのステージにフォームがおいてあり、それぞれに超基本的なXSSが可能なので、アラートを出してねという問題。簡単に書くと

  • level1:<script>alert(1)</script>
  • level2:<script>が消されるので<s<script>cript>alert(1)</script> or <scirpt >(スペース)
  • level3: hiddenタグがエスケープされていないので埋め込む 
  • level4: iが1に変えられるので<scrIpt>alert(1)</scrIpt>
  • level5: <scriptが消されるので<scri<scriptpt>alert(1)</script> 

こんな感じでした。簡単ですね

練習問題終了後は脆弱性を作りこんだ本番のような環境で演習を行いました。

掲示板を模したWebサービスを使用して書き込み回数が多かったほうが勝ちというものでした。(知っている人は知っているアレ)掲示板にXSSができるので色々やってねと言うもの。アラート出たり飛ばされたり色々と面白かった。

 

最後の演習は普通のWebサイトを模したもので演習を行いました。XSS複数個とphp脆弱性ディレクトリトラバーサルなど見つけました。最終的にphpスクリプトを注入して探索して、user情報とadminアカウントのハッシュをゲットしたので、レインボーテーブルでホイホイしてadminになり、トップページを乗っ取って終了。楽しかった。

 

Webアプリケーションでもヘタすればサーバーごと乗っ取られちゃうよということで、対策をしっかり行わなければならないなと思いました。

 

 

 

夜は会議室で吉井先生からの法と情報セキュリティ技術の使い方の講義でした。レッドブル効力が切れていたので結構眠気が襲ってきましたが、ディスカッションを合間に入れてくださっていたので眠気が吹っ飛びました。ディスカッション楽しかった。

3日目

 

最終日は一番楽しみにしていた濱本さんによるハニーポットの解析演習でした。

 

はじめはネットワーク探索の基礎などを学びました。pingやnmapなど基本的なツールを使用して調査を行う手法を学びました。

 

攻撃者によってめちゃくちゃにされたあとのハニーポットの中身を探索し、どんなコマンドが使われているのか、何のマルウェアがどこに入っているのか。何をされたあとなのかを調査しました。/var/log/を漁ったり、コマンドのログを漁ったりしてなにか変な通信がないか、なにか変なコマンド実行してないかを一つ一つ見ていくのは大変でした。途中でftpで謎のファイルを落としていることを班員の方が見つけてくれたので、その周辺を調査するとルートキットを見つけました。見慣れないディレクトリやファイルが一緒に設置してあったので、調査を続けていたところでタイムアップ。とても楽しかった。

 

難しかったことは、/var/log/やhistoryなどを漁る場合、通常のコマンドや正常なトラフィックが入り交じっていて、どこが危険なのかどの部分が怪しいのかを判断することでした。また、濱本先生から「lsコマンドは汚染されています」と言われるまでlsコマンドが改ざんれていることに気づきませんでした。findなどは改ざんされていないとの事だったので、$ find . -maxdepth 1にエイリアスを貼って使用。こんなこともあるんだなと非常に勉強になりました。

After

今回記事にしたのはほんの一部で、その他にも沢山の技術や法的な解釈、モラル、現状などを学ぶことが出来ました。九州でのセキュリティ・キャンプは今回が第1回ということで、記念すべき第1回目の卒業生になれてとても嬉しいです。今後セキュリティ・キャンプ九州は続いていく(よね!?)と思うので、非常に期待しています。

 

実行委員や講師の方々、また支援して頂いた方々、参加者の方々ありがとうございました。

 

おまけ

サイバー人材育成急げ 福岡で合宿講習 : 最新ニュース : 読売新聞(YOMIURI ONLINE) < http://www.yomiuri.co.jp/kyushu/news/20140830-OYS1T50071.html >

 

新聞に掲載されました。

 

 

LINE乗っ取り詐欺が来たのでアクセス元を特定してみた

2014年08月04日18:30頃、次の日の試験勉強をしていると一通のDMが花田さん(@decoy_service)より届きました。

f:id:zipsan:20150215012656p:plain

!?

f:id:zipsan:20150215012700p:plain

ということで、やってみました。

先日SECCON Onlineでソーシャルハックという問題がありましたが、まさかこんな形で実践することになるとは。

続きを読む

SECCON 2014 quals Online あみだくじ Writeup

先日のSECCON 2014 quals OnlineのProgramming 300 あみだくじを試合終了後にときましたので、置いておきます。

二時間程度かかりました。もうちょっと早く綺麗に解けるようにならないとな・・・。

Prog300 あみだくじ

問題ファイル:amida

実行ファイルが渡されるので、解いてねと言う問題。

書いたプログラムが以下。(python3)

# -*- coding: utf-8 -*-
import sys
from subprocess import Popen, PIPE

# 元の文章を渡すとhead(数字行), mondai(あみだ本体), tail(*行) のリストを返す
def transform(original):
    # 最初のNo~と最後の空白行を削除
    # top =&gt; 最初の行, bottom =&gt; 最後の行
    original = original[1:-1]
    top = list(original[0])
    bottom = list(original[-1])

    # あみだが横に倒れている場合
    if "".join(top).find('--') != -1:
        trans = []
        for m in original:
            t = m.replace('-', '#')
            t = t.replace('|', '-')
            t = t.replace('#', '|')
            trans.append(t)
        trans = list(map(list, zip(*trans)))  # 転置
        original = ["".join(tr) for tr in trans]
        top = list(original[0])
        bottom = list(original[-1])

    # 最終行が数字で始まっていたら上下を反転
    if bottom[0] == '1' or bottom[0] == '8':
        trans = []
        for m in original:
            trans.insert(0, m)
        original = trans
        top = list(original[0])
        bottom = list(original[-1])
   
    # 1 2  3  4    5 6 7    8 のように空白が開いている場合は、各行から冗長部分を削除
    if (top[0] == '1' or top[0] == '8') and "".join(top).find('  ') != -1:
        i = 0
        j = 0
        while i &lt; len(top) - 1:
            while top[j:j + 2] == [' ', ' ']:
                for k in range(len(original)):
                    tmp = list(original[k])
                    del tmp[j + 1]
                    original[k] = "".join(tmp)
                del top[j + 1]
            i += 1
            j += 1

    head = list(original[0])
    body = original[1:-1# headとtailを除いた部分
    tail = list(original[-1])

    return (head, body, tail)

def main():
    p = Popen(["./amida"], stdout=PIPE, stdin=PIPE, shell=True)

    while True:
        print('=' * 50)

        # 標準出力から問題を取得する
        msg = ""
        while True:
            c = p.stdout.read(1)
            if not c or c == b'?':
                break
            msg += c.decode('utf-8')
        print(msg)

        original = ["".join(list(m.replace('\x00', ''))) for m in msg.split('\n')]
       
        # 整形処理
        head, body, tail = transform(original)
        print("#" * 30)
        print("".join(head))
        print("\n".join(body))
        print("".join(tail))
        print("#" * 30)

        # solve
        for i in range(len(body)):
            for j in range(len(body[0])):
                if body[i][j] == '-':
                    # swap
                    tmp = head[j - 1]
                    head[j - 1] = head[j + 1]
                    head[j + 1] = tmp

        answer = head[tail.index('*')]
        print("Answer:", answer)

        # 答えを投げる
        p.stdin.write((answer + '\n').encode())
        p.stdin.flush()

           
if __name__ == '__main__':
    sys.exit(main())

渡された問題を標準出力から読み取り、整形して、解いて、標準入力から渡しています。

結局あみだくじは橋が出てきたら数字を入れ替えて行って、入れ替え終わったら*の位置と照合するだけでいいので、整形のほうがメインになってきます。

この問題、問題が進んでいくたびに上下が逆になったり、橋の間隔が大きくなったり、横になったりするので、それぞれに応じて整形してあげなければいけませんでした(ここが一番つらかった)

問題の出現順序が変わらなかったので、「一問あたりの制限時間あるけどどうせ100くらいなら手作業でいけるっしょ〜w」と思い、競技中は順番に入力して解いてたんですが、100問超えくらいから徐々に怪しくなって、160問くらいでつらくなったのでやめました。

その後、やっぱスクリプト書こう!!ってことで書き始めたんですが、もう考える気力と集中力は残ってなかった。結局解けず・・・(あたがわさんに任せとけばよかったなぁ・・・)

実はPythonのsubprocess.Popen使って標準入出力を制御する方法がわからず、ぐぐっても見つけきれなかったので始めは

$ netcat -l -p 40000 -e ./amida

して、それに対してTelnetlibから入出力を受け取って解いてました w

メンバーさんに聞いたらやっぱりPopenでできるみたいだよ!とURL付きで教えてくれたので、それを参考に組みました。勉強が足りないな・・・・。

SECCON 2014 quals Online 重ねてみよう Writeup

SECCON 2014 quals Onlineに参加しました。今回はビギナーでも解ける問題をたくさん出題されるということで、少しは貢献できるかな?と思いましたが、人権消滅しかけてます。

チームはwasamusumeです。今回もチームメンバーの皆さんがスパッと解決していました。すごい。

amidaも中途半端だし、他の問題はメンバーさんが強すぎてタイミングなかったので本当は呼吸Writeupになりそうだったんですが、スキマを縫ってFlag提出したものがあるので書いておこうと思います。

Prog100

重ねてみよう
ジャンル:プログラミング
点数:100
問題文:afterimage.gif

afterimage.gifがもらえるので、開くとGIFアニメのよう。

f:id:zipsan:20150214060921g:plain

どう見てもQRコードなので、Pythonスクリプト書きました。

# -*- coding: utf-8 -*-  
from PIL import Image  
  
X = 164  
Y = 164  
canvas = Image.new("RGB", (X, Y), (255, 255, 255))  
  
for i in range(1, 50):  
    print("img/img_{0:02d}.png".format(i))  
    base = Image.open("./img/img_{0:02d}.png".format(i))  
    pixels = base.load()  
    for x in range(X):  
        for y in range(Y):  
            if pixels[x, y] == 1:  
                canvas.putpixel((x, y), (0,0,0))  
  
canvas.save('ans.png', 'PNG')  

GIFアニメを適当なツール(今回はGiam)でつるっと抽出して、img/ディレクトリに保存。

PILライブラリを使って一個一個開きつつ、白い部分だけキャンバスに転写していきます。(上のスクリプトは白い部分を黒にして転写しています)

前回の記事や他のCTFで予習してたので調度良かった。

f:id:zipsan:20150214061125p:plain

出力がこれです。読み取ると

FLAG{Many dot makes a QR code}

がでました。

 今回はいくつかの問題しか手を付けていないので、復習しておかなきゃなと思ってます。

とくにあみだくじはめちゃくちゃ悔しいのでリベンジしておきます!解けたら記事にしようかな。