SQLインジェクション(弊研究室の某課題について考える18日目)

はじめに

失踪はしない

弊研究室の某課題について考える18日目の記事です.

SQLインジェクションの簡単な説明です。またこの記事はこのような攻撃手法があるので注意しましょうという目的であり、攻撃を推奨するものではありません。

SQLインジェクション(SQLi)とは

SQLインジェクション(英: SQL Injection)とは、アプリケーションのセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のこと。また、その攻撃を可能とする脆弱性のことである。

例えば下のようなphpファイルでsqlにアクセスすることを考えます。(わざと悪い方法を使って書いています)

<?
if(isset($_POST["send"])){
    $id = $_POST["id"];
    //データベース処理
    $dsn = sprintf('mysql: host=%s; dbname=%s; charset=utf8',$db['host'],$db['dbname']);
    try{
        $pdo = new PDO($dsn,$db['user'],$db['pass'],array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION));
        $query = 'SELECT * FROM list WHERE id = '.$id;
        $stmt = $pdo->prepare($query);
        $stmt->execute();
    }catch(PDOException $e){
        $errorMessage = "データベースエラー";
        echo $e->getMessage();
    }
}
?>

HTMLはこんな感じ

<form method="post">
<label for="id">番号</label><input type="text" id="id" name="id" placeholder="数字を入力してください">
<input type="submit" id="send" name="send" value="送信">
</form>

さて$query = 'SELECT * FROM list WHERE id = '.$id;の部分に脆弱性があります。$idがそのまま文字連結されていますが、この$idはユーザの入力によってどのような入力であってもよい形になっています。

この$id1 or 1=1と入力すると$query = 'SELECT * FROM list WHERE id = 1 or 1=1'となります。するとwhereの中のid = 1 or 1=1trueなのでlistテーブルの全出力が得ることができます。

また例えば$query = 'SELECT * FROM list WHERE id = '.$id.' and passwd = '.$passwdといったqueryがあった際にid1 or 1=1 --とするとクエリが$query = 'SELECT * FROM list WHERE id = 1 or 1 = 1; -- and passwd = '.$passwdとなります.

--sqlコメントアウトなのでpasswdの値がなんであってもこのsqlは正しく通ります。

ブラインドSQLインジェクション

SQLインジェクションを利用すれば様々なクエリを動かすことが可能です。ですがテーブルの情報を消したり更新したりするにはテーブルのカラム名等を知る必要があります。そういうときに使う攻撃方法がブラインドSQLインジェクションになります。

ブラインドSQLインジェクションとはSQLインジェクション脆弱性は存在するがSQLの結果やエラー表示等が見れないときにでも成立する攻撃方法です。SQLインジェクションは存在するのでクエリの実行判定で確認します。

クエリの実行結果が確認できる場合は単純です。一文字ずつ確認していきます。

上の例においてid1 and substr(pass,1,1) = "A" --とすることでpassの一文字目がAであれば実行できます。アルファベット順で試すと時間がかかるので2分木探索をします。

1 and substr(pass,1,1) > "N" --とかこんな感じで定めていくことで効率よくやります。

ではクエリの実行結果が確認できない場合はどうするかというとsleep関数を用います。

クエリが正しく実行された場合はsleep関数によって定められた時間とまるはずなので止まった時間を用いてクエリを判定します。

参考

「ブラインドSQLインジェクションとは何ですか?」への回答 - 徳丸浩のtumblr

RSAの危ない運用(弊研究室の某課題について考える21日目)

はじめに

この記事は弊研究室の某課題について考える21日目の記事です。

前回RSAの暗号についてやったので今回はRSAの危ない運用に関して話します

公開鍵Nのビット数が短いとまずい

公開鍵Nのビット数が短いと普通に計算して素数を求められます。そうするとpとqがわかってしまうのでまずいということで今回は短いビット数の鍵を復号します。

Pollard-Rhoアルゴリズム + Miller-Rabin素数判定法を用いて素数を見つけます。素数となる数でどんどん割っていく方法だと時間がかかるのでしょっぱなからこれでいきます。

import sys
from random import randint
from fractions import gcd
from Crypto.PublicKey import RSA

def miller_rabin(n,k=20):
    s, d = 0 , n-1
    while d % 2 == 0:
        s+=1
        d/=2
    for i in range(k):
        a = randint(2,n-1)
        y = pow(a,d,n)
        if y == 1:
            continue
        for j in range(s):
            if y == n-1:
                break
            y = (y*y) %n
        else:
            return False

    return True

def pollard_rho(n, c=1):
    x, y, d = 2, 2, 1
    while d == 1:
        x = (x*x + c) % n
        y = (y*y + c) % n
        y = (y*y + c) % n
        d = gcd(abs(x-y),n)
    if d != n:
        return d

#N = 821 * 953

key = RSA.importKey(open("96key.pem").read())
print key.n

print pollard_rho(key.n)
$ time python pollard_rho.py
67347099272184651187705110721
239973115325831

real    2m25.078s
user    2m25.004s
sys     0m0.017s

こんな感じで素因数分解の答えを出してきます。意外と時間がかかるので自分の計算機でどこまでの鍵なら計算できるのか知っておくと便利かも。自分は128の鍵は30分以上かかりました。

素数pとqの値が近いとまずい

選んだ素数pとqの値が近いとフェルマー法によって求められます。

Fermat法は素数同士の積からなる合成数において、二つの素数の差が小さい場合に有効なアルゴリズムである。 具体的には、合成数nの平方根周辺のある値をxとしたとき、(x-k)*(x+k) == nとなるkを小さいものから求める。

p*qの積のNがあった際にNより大きいa^2を考え N = a^2 - b^2とあらわすことができれば、a+ba-b素因数分解の結果となるということです。

まぁ書きます。手順としてはNに近い平方根を計算すること。そしてそれに近いbを試していくことって感じ。まぁbを試すためにはxを増やしたりyを増やしたりって感じで試していく

def isqrt(n):
    x = n
    y = (x + 1) // 2
    while y < x:
        x = y
        y = (x + n//x) //2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)

    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1

    return x+y, x-y

n = 0x86E996013E77C41699000E0941D480C046B2F71A4F95B350AC1A4D426372923D8A4561D96FBFB0240595907201AD3225CF6EDED7DE02D91C386FFAC280B72D0F95CAE71F42EBE0D3EDAEACE7CEA3195FA32C1C6080D90EF853D06DD4572C92B9F8310BBC0C635A5E26952511751030A6590816554E763031BCBB31E3F119C65F

p,q = fermat(n)

print p
print q

実行結果は下のように

$ time python fermat.py
9733382803370256893136109840971590971460094779242334919432347801491641617443615856221168611138933576118196795282443503609663168324106758595642231987246769
9733382803370256893136109840971590971460094779242334919432347801491641617443615856221168611138933576118196795282443503609663168324106758595642231987245583

real    0m0.015s
user    0m0.007s
sys     0m0.008s

複数のRSA暗号があるとき同じ素数を使っているとまずい

つい最近のCTF問題でもありましたね。Common's factors attackと言われるものです。

これですが複数のRSA公開鍵のNから最大公約数を求めればいいです。問題を用意するのが大変なので今回はとある問題から素数合成数をいただきます

用意する問題は当然ps and qsです。

今年のsecconの問題ですね。私も解いたんですがその時にはBKPのやつを借りました。今回は時間に余裕があったので一からコード組みますっていっても公約数求めるだけなのでたいしたことはない

from Crypto.PublicKey import RSA
from fractions import gcd
import gmpy

E = 65537

def common_factor(N1, N2):
    p = gcd(N1,N2)
    return (p, N1/p), (p,N2,p)

def form_priv_key(n,p,q):
    phi = (p-1) * (q-1)
    d = gmpy.invert(E,phi)
    key = RSA.construct((long(n),long(E),long(d),long(p),long(q)))
    return key

key1 = RSA.importKey(open("pub1.pub").read())
key2 = RSA.importKey(open("pub2.pub").read())

print key1.n
print key2.n

privkey1, privkey2 = common_factor(key1.n,key2.n)

privkey = form_priv_key(key1.n, privkey1[0], privkey1[1])
print privkey.decrypt(open("cipher").read())

privkey = form_priv_key(key2.n, privkey2[0], privkey2[1])
print privkey.decrypt(open("cipher").read())

結果はこんな感じ。

$ python2 common_factor.py
847796795638781450678718708664542960446354226336422534142899480441478781747168340722544245493739168125415291376063352480076469305992008517388366298914970810765149321160596112226917023146371080685458239747986992197343482255681414590678694753885521068656675164266739274608505094927725285868801825144058835115051061857233824816176186349372737400492228548130718029270482313906040935333159681273527671359469173939861889228505791626525994180917670701909816457560622922219797109618405842358250940062127841301193206255543580550667149741357342408703680375118252445296600962244008043713708516004025217199954286478272550961283413053789743740774140439284662596273452124779590753836094450154459078034659421366584354005421566290862244000178569163732971816333961711759000171156293724203334889496589376907683142167883476184246287660764821622298961378903818077158234683927321823293466245584486980446609569799420950073036635602004293246754252079757527610246223280774592566063422181158810677919909523144033543195999214800559998598003329294711989868113423468536273984117507008756542731210465222475943122324672467669623079721727526348599823126409108190030551584092950358235465140166515452377475799157617227362838246912839086733412861436353117994501342653
763718912475160487902804749669814117361530270298225094625871588939939773892509348006077810445741086683427253000695920011673348476297973798322806091336777405584801442639626925406721932533140226556519019440300864340670199686368307155860493615065198319490060598587202051942638792919648596288576294804549738135969737494734307362891313864027749187674251692407867312885251279302785352318725391842117065840058358065676707016006124478822206302825992616559261930620693061673348139416033418864269248381876692676529410115745518353146254670349865568255213560376368953292931958006941630719304442332912813624543600126197554727832190226632919876204677667384620275620336951964833888599634720101911051166398907898747710391394105753614253527704990658698844796442515669670816004761855554187277637871343595647487793209271354240148469085627742503649786300484610102224828274384484809539697728008552925590472129497180290668277790132130824141651399551803499770513576176720509094833332201946177880267399933460994496277590932311628302240154240967341858152145815276163397709272690500041597393678630136932574450837982593210399370333578887450410911663220219423601973078501237709613593311133945501455828164291429228495931943107997137587307522565029820756690578833
▒H4؋ѝ2l▒r
▒puF`u▒܎▒▒3▒4CT▒R
  ▒▒V▒Z[]▒▒▒▒%▒▒)() _▒d▒
                        |▒o▒h▒o▒V▒▒s{t_▒vӫ▒1ۧ▒[:▒X▒Y▒▒%▒%▒▒r▒9dJ▒|▒▒▒▒▒▒▒7▒▒M▒▒
                                                                              g▒B▒▒▒▒݁t^Scq▒`H▒0▒c▒f▒▒▒▒Ŏ\▒
                                                                                                          6▒a ▒▒"5`▒_▒et:
▒▒a▒C   =▒L▒X▒  UmL▒b▒'▒▒▒\▒X▒▒▒3▒▒l>▒▒(W!▒▒T▒▒▒▒e▒DǷ.▒▒?f▒L.▒U▒▒▒▒▒▒nR~J▒.▒'▒&/▒▒w▒▒▒▒!A▒wS"U▒▒Z▒*▒    ▒▒▒E▒T▒d▒w▒@▒J!▒=▒▒▒▒"▒▒,▒▒▒▒Dh▒▒▒▒▒▒      ▒▒▒▒(▒l▒▒▒4UayXg4Ї4Z▒Z▒▒Ma▒▒5▒▒▒|▒ۻ▒;Z▒-S▒@▒▒]> \n6>▒g▒MBK▒▒E▒@▒a▒VYԹB▒p▒▒▒`▒[▒53▒1▒],▒GA".▒
         ▒▒5▒摨SECCON{1234567890ABCDEF}

▒▒C@▒▒&▒▒▒▒▒▒?▒w▒▒▒9▒y▒N>▒g▒▒▒A▒n▒<Sy▒▒▒▒▒▒▒ڍ▒▒E▒T▒8T▒▒X▒ލ▒O>ƾ▒H▒▒▒`▒=▒
                                                                       ▒▒R▒▒▒▒0
                                                                               ƞ▒R▒8Η▒\o"▒▒Kf+8▒▒5o▒V▒=
                                                                                                        ▒E▒▒▒▒yv▒▒C▒>▒▒Όme[▒▒e
           ▒螉\MNu4z▒▒▒}▒▒▒4 ▒Gf▒Ȯ▒▒▒▒▒P▒▒▒"K▒▒Ќ>▒aW▒▒▒)4▒C▒ϿK▒▒▒T▒0▒/▒B▒T▒]YL@-▒▒▒▒ ▒e▒*▒Ҟ▒ӥ▒▒▒▒▒▒}▒֫▒DŢ<R%Ƙ▒Z▒<▒y[▒PI▒▒T▒e1▒▒▒l▒T▒p.▒▒▒t[▒ko▒$▒G▒▒▒~i▒▒▒▒▒▒▒▒$▒▒▒:Z|▒▒u▒c▒▒▒0▒k▒OBO▒kZ▒CV▒▒6▒▒▒▒▒ $>▒ԃBf▒V▒f▒▒▒.<o▒▒▒▒Q~▒j▒▒`j

終わり

まだまだ危ない運用はあるのですがひとまずはこんな感じでよいでしょう。私の勉強のためにもここらへんはまとめておきたいものですね

参考文献

RSA暗号(弊研究室の某課題について考える20日目)

はじめに

失踪は許さない弊研究室の某課題について考える20日目の記事です。

他の記事も徐々に書いていますが完結は年明けになりそうです。

RSA暗号

RSA暗号は桁数の大きい合成数素因数分解は難しいという理論の元で構成された公開鍵暗号です。

共通鍵暗号公開鍵暗号

暗号方式には共通鍵暗号公開鍵暗号があります。共通鍵暗号は暗号化と復号に同じ鍵を用います。公開鍵暗号では公開鍵と秘密鍵の2種を用意し、片方の鍵で暗号化したものはもう片方の鍵でしか復号できないといったものとなります。

共通鍵暗号では暗号化と復号に要する鍵が同じであるため鍵を安全に相手に渡す必要があります。

一方で公開鍵暗号では秘密鍵は自身でもっておくものであるため、公開鍵等の情報を用いて秘密鍵を推測、生成されないようなアルゴリズムである必要があります。

RSA暗号の仕組み

  1. 素数pとqを選びます。
  2. n=p*q, φ(n)=(p-1)*(q-1)とする
  3. φ(n)と互いに素となるeを選ぶ
  4. d * e ≡ 1 (mod φ(n))となる最小のdを選ぶ
  5. neを公開鍵、p,q,d秘密鍵とする

RSA暗号鍵を使った暗号化と復号

2つの方法で暗号化・復号を行いたいと思います。

  1. opensslコマンドを利用してRSAの公開鍵と復号鍵を作成して暗号化・復号する
  2. 素数pとqを作成し、自力でRSA暗号鍵を作成して文書を暗号化・復号する

opensslコマンドを利用する

まずは暗号鍵を作ります

$ openssl genrsa > server.key # 秘密鍵の作成
$  openssl rsa -in server.key -pubout -out publick-key.pem # 公開鍵の作成

opensslコマンドの秘密鍵には素数pとqが含まれているので公開鍵を作成することが可能です。 openssl rsa -text -in server.keyで確認できます。

ではこの鍵を用いて暗号化していきます

暗号化にはopenssl rsautl -encryptを用います

$ echo "This is a plain text" > plain
$  openssl rsautl -encrypt -pubin -inkey public-key.pem -in plain -out encrypt
]$ cat encrypt
N▒-9U▒
▒▒%`▒;▒▒▒1▒▒=%2u'm▒▒
                    ▒▒֡▒L7▒돆▒▒▒m▒▒W▒▒@T▒
▒L
V▒rU▒▒▒@we▒▒▒-@<▒▒^▒▒▒H▒v▒|^Й▒▒1▒▒{&j*▒GFZ|t▒Q▒▒W{▒▒Q.▒%▒▒▒
                                                           ▒
V▒▒4▒|u▒▒▒Q▒V
▒5▒i▒viyk▒▒![M▒▒,▒ȥh▒▒Ȉ6묛▒▒ң▒l\d▒#▒Dt.▒D̃)sdZ%o}tS`▒▒0▒▒q

次に復号です。復号にはopenssl rsautl -decryptを使います

$  openssl rsautl -decrypt -inkey server.key -in encrypt
This is a plain text

さてでは作った鍵をpythonを用いて暗号化・復号していきます。CTFではよくpythonスクリプト書きますからね

pythonでopensslで作成した鍵を取り扱う

pycryptoライブラリを使用します。

$ sudo pip install pycrypto

こんな感じで暗号化・復号ができます

from Crypto.PublicKey import RSA

with open("encryptpy.txt","w") as f:
    f.write(RSA.importKey(open("public-key.pem").read()).encrypt(open("plain").read(),"")[0])

with open("decrypt.txt","w") as f:
    f.write(RSA.importKey(open("server.key").read()).decrypt(open("encryptpy.txt").read()))

ちなみにopensslコマンドで作成したencryptファイルの復号もできます。

>>> from Crypto.PublicKey import RSA
>>> privkey = RSA.importKey(open("server.key").read())
>>> privkey.decrypt(open("encrypt").read())
'\x02\xb9\x9c\x19\xef~\'\xd9\x93\xa6}2#"\xf0oMH\x86\x9d\x83\x98\xf2M\xda\x97\xdbT\xa9WW_\xb7y\xb8\x03\x1a\xf7P\xc5\xbe\x92\n\xf9^aOA\xae\xe4\xea{\x03?\xaf\x8dX\xe2\xc1Oe\xf8\xa0Z\x96\x87[\x87\xfc\xe2F\x13\xf7u\xd1\xd5sDO\xf7\x11\xef\xc3\x0c\x17\xd3\xa8\x03\xa4t\xd0\xa4O\xdezA?\x04\x01cM\xb8\xb7\xa3y6\xbd\xab\xb6\xf6j\x13\xef\n/\rR\x956\xeffD\x1e01M\x13{\x16\x11\xde\xad\xc5\x18\xbcN\x99\x99F\x85\x99(\x0f\x0byE\xb0\x91\xa0\xd2V_\x90\xc2\x97xG\x05\xc8\xacd\x0e&\x996\x8fl\xbe\xb7\xde\xfb\xc1\x1c\xea,\r\xf7\xa7\x86\x87`\x8fD:\xd0\xd4\x9e\xb1\xd4*Q\xc6\xf7\x1d\x14\xeb?\xccA\xee\xbd\xb1\x82>\x08\ry\xde\xe4\xa2\xd2\xc8\xb3\x7f\xb6e\xc1\xf1\xa3\xbcDa\xae_e)\x19P\x94\xf7\xdep@\x00This is a plain text\n'

しっかり復号できていることが確認できます

pythonで自分で鍵を作成する

RSAアルゴリズムに従ってpythonで鍵を作成します。今回素数p,qは簡単なものを利用します。

from Crypto.PublicKey import RSA
from fractions import gcd

p = 661
q = 821
e = 65537

N = p*q
L = (p-1)*(q-1)

for i in range(2,L):
    if (e*i) % L == 1:
        D = i

key = RSA.construct(map(long,(N,e,D)))
print key.exportKey()
$ python makersa.py
---BEGIN RSA PRIVATE KEY-----
MCUCAQACAwhH2QIDAQABAgMD6qECAgKVAgIDNQICAlECAQ0CAgGZ
-----END RSA PRIVATE KEY-----

ということで秘密鍵が作成できました。鍵サイズが1024bitないのでpycryptoで暗号化・復号はできません。CTFの問題なら1024は超えるはずなのであとは上の時と同じように復号してください。

終わりに

次はplain RSAに対する攻撃という題でRSAのいけない運用に対して話していこうかなと思います。