多くのPoCがpyhtonで書かれている
脆弱性情報が開示される際にポイントとなるのはExploitコード(攻撃コード)が公開されているかどうかです。セキュリティ分野では実証概念、PoC(Proof of Concept)を脆弱性を実証するためのプログラムを意味していてこの情報をtwitterなどの情報源をいち早く漁ってくる必要があります。
そしてセキュリティ担当は「それが定かであるか」「自社の環境で実行されるのか」を検証しなければなりません。
しかし、このような攻撃コードはいい加減なガセならまだしも、それを実行することで環境が汚染されたり、感染したり、破壊したりする可能性があるため見つけたExploitコード(攻撃コード)でそのまま検証することはあり得ません。
Exploitコード(攻撃コード)を読み解き安全性を確認した上で必要な環境を用意し実行します。
とはいえ、PoC(Proof of Concept)のpyhtonはそこまで複雑なソースコードは少なく、①リクエストやコマンドを送信して②応答を解析して③脆弱性がある場合の痕跡を確認するが基本の流れです。
最近ではGo言語のPoCも出てきているがpythonが多いという状況はすぐに変わるとは思えません。
ローカルエクスプロイト(BOF系)などはC言語が多くなります。
検証環境はDockerで用意する方法が片付け簡単でオススメです。
「危険な」Exploitコード(攻撃コード)見抜き方
脆弱性の判断に関係ない部分があり、自社環境から外に、逆に外から自社環境に通信が発生したりファイルが生成されるような場合を「危険な」Exploitコード(攻撃コード)と判断しています。
もしセキュリティアップデートのコードが公開されている場合、差分を確認して見ることをオススメします。その部分の脆弱性を突くために必要な処理なのかどうかが「危険な」Exploitコード(攻撃コード)を見抜く強い指針になるからです。
また、PoCの作成者が自身の環境で試したまま公開してしまっているケースもあり、そのまま実行してしまうと意図せずその指定先に飛んでしまう可能性があるのでやはり事前の読み込みが大切になります。
怪しげなリクエスト先(URL)が指定されている
そのまま書いてあることは少ないのですが、エンコードや暗号化された部分に外部と通信を行う箇所がある場合があり、意図しない情報の転送や意図しないファイル(時にはマルウェア)がダウンロードされます。これが判明した時点でそのコードを利用するのを止めるべきです。
必要ないファイルを生成、ファイルに書き込みをしている
どさくさに紛れてファイルを生成したり。ファイルに書き込んだりする処理が含まれているのは明らかに怪しいです。さらに最終的にはファイルを削除したり、設定を戻したりしているのは怪しいことをしている証拠と言っていいでしょう。
さて、晴れて「危険な」Exploitコード(攻撃コード)でないことが判明したらコードを読み解いていきましょう。
if __name__ == “__main__”:が必要な理由
結論から言うとインポートされた際にプログラムが勝手に動かないようにするために書きます。
Pythonでは、インポートされたファイルの中身は実行されます。これでは再利用するためのインポートの度中身が実行されることになります。
Pythonでは、__name__ にPythonファイルのモジュール名が入ります。
import sample された場合、 sample.py 内部での __name__ は “sample” になります。
python sample.py した場合、sample.py 内部での__name__ は “__main__” という文字列になります。これにより明示的に実行された時のみ実行するように制御することができるようになります。
まずはリクエストの送信とレスポンスデータの取得
書き方が何パターンかあります。コードを書く分には1つ知っていればいいですが、読むためには複数パターン知っているといいでしょう。
import httplib
import urllib
#サーバコネクションを生成
conn = httplib.HTTPConnection("https://sample.com")
#リソースの要求
params = urllib.urlencode({'id': '123'})
conn.request("POST, "/search", params)
#200応答であることなどを確認の上データを取得する(PUTの場合は201など臨機応変に)
b = conn.getresponse()
h = dict(conn.getresponse().getheaders())
if r.status == httplib.ok
body = b.read()
header = h.read()
#サーバコネクションを削除
conn.close()
OSコマンドの実行はos.systemではなくsubprocessで
CVE-2019-14287など脆弱性は、$ sudo -u#-1 id -uで0が返ったら脆弱とコマンドを打った方が早いかもしれませんが、ダウンロードしたファイルを解析する場合など様々な場面でOSコマンドのお世話になることがあります。
os.systemはsubprocessに統合され廃止予定なので
os.systemではなくsubprocessモジュールを利用しましょう。
runメソッドは,コマンドの終了を待ちます。
コマンドを動かしながらプログラム側でも処理をしたい場合には,Popenオブジェクトを使いましょう。
“stdout=subprocess.PIPE”は標準出力を取得したい場合につけます。
subprocess.Popen.communicate()とするとsubprocess.Popen.stdout.read()、subprocess.Popen.stderr.read()でも出力を読むことができます。
注意すべきはバイト列で返るためprintではなく次のように記載します。
sys.stdout.buffer.write(res.stdout)
import subprocess
stdout,stderr =subprocess.Popen(
['ls', '-la'],
encoding='UTF-8',
stdin=subprocess.PIPE,
stdout=subprocess.PIPE).communicate()
正規表現で一致部分を探す
「③脆弱性がある場合の痕跡を確認する」では応答から正規表現による検索により脆弱性の痕跡を抽出する場合があります。例えば以下のようにするDBパスワード記載部分を探すことができます。
import requests
import re
re.findall(r"(?<=DB_PASSWORD\', \')\w.*?(?=\')",requests.get(url,verify=False).text)
SQLコマンドの実行は実行したら必ず閉じる
SQLコマンドを実行するPoCはあまり多くはありませんが知っておきましょう。
con = MySQLdb.connect(user='root',passwd=mysql_password,host=host,db=db_name,charset="utf8")
cur= con.cursor()
sql = "select user_pass from wp_users where ID=1;"
cur.execute(sql)
result = cur.fetchone()
cur.close()
con.close()
スクレイピングや文字列操作系の関数が利用されます
文字列連結や文字列比較、さらにはパーサー(JSON)など基本的な関数に加え最近ではスクレイピングに関連した技術も利用されている場合があります。
レスポンスのリンクを抽出するのであればBeautifulSoupで以下のように簡単に抽出できます。
import requests
from bs4 import BeautifulSoup
try:
r = requests.get(url,headers=headers)
html = r.content
try:
soup = BeautifulSoup(html, "html.parser")
for a in soup.find_all("a"):
link = a.get("href").get_text()
print(link)
except Exception as e:
sys.stderr.write(str(e))
このほかにももちろん様々な関数が利用されていますが、検証用のコードなのでそれほど複雑なコードは多くありません。どちらかと言うと脆弱性自体の動作と前提としている環境が想像しづらいことがネックになる場合が多いのではないでしょうか。
pythonコードを読むハードルを超えてPoC、Exploitコード(攻撃コード)の検証のお役に立てれば幸いです。
以上です。参考になれば幸いです。