2021 OWASP トップ 2: 暗号化エラー

1. 暗号化失敗の脆弱性の概要

暗号化は、開発シナリオに適用される数学的問題です。実際、暗号化機能はブラックボックスのようなもので、開発者が考慮できるのは入力と出力だけであり、出力は依然として非常に複雑です。暗号化の成功はシステムのセキュリティに大きな影響を与えますが、多くの開発者は暗号化について深く研究していません。したがって、開発者が暗号化を検討するときは、暗号化の品質を検証せず、暗号化された出力が成功したかどうかのみを気にすることがよくあります。

国内の情報セキュリティ構築において、システムデータのセキュリティの重要性はますます高まっており、まず考慮すべきはデータ伝送層とストレージ層のセキュリティです。これらのリンクで使用される主な保護スキームは暗号化であり、現在開発のあらゆる側面に浸透しています。暗号化の使用例の一部を次に示します。

  • データは平文で送信されますか?
  • 業務システムに古いバージョンや強度の低い暗号化機能はありませんか?
  • サーバー上の証明書は合法かつ有効ですか?また、証明書の信頼チェーンは完全ですか?
  • 暗号化機能の初期化シーケンスは適切に使用されていますか?
  • ECB などの安全でない暗号操作が使用されていますか?
  • 乱数は適切に初期化されており、ハードコードされたシードが使用されていますか?
  • 暗号化エラーやサイドチャネル情報により暗号が解読可能になりますか?

これに対応して、暗号化攻撃のシナリオは次のとおりであることがよくあります。

  • データベースの安全でない暗号化
  • 平文でのデータ送信
  • 暗号化の強度が十分ではありません
  • 弱hash
  • 不適切な署名検証

2. 実際の戦闘シナリオ - RSA 低暗号化インデックス攻撃

RSA暗号化のレビュー

攻撃方法を検討する前に、RSA 暗号化プロセスを確認してみましょう。

plain_text = 明文,cipher_text = 密文,(n,e) = 公钥,(n,d) = 私钥
加密过程:plain_text ^ e ≡ cipher_text (mod n)
解密过程:cipher_text ^d ≡ plain_text (mod n)

低暗号化インデックス攻撃の場合、一定の条件は次のとおりです。

密文:cipher_text;
公钥:(n,e)。

攻撃の目的は平文情報 (plain_text) を取得することです。

事件戦闘

実際の戦闘シーンはミステリー射撃場にあります。射撃場に入ると、RSA 低暗号化インデックス攻撃シーンを選択して開くことができます。

環境を起動すると、ホーム ディレクトリに flag.enc と pubkey.pem という 2 つのファイルが見つかります。

total 16
-rw-rw-r--@ 1 hunter  staff  512  6  2  2019 flag.enc
-rw-rw-r--@ 1 hunter  staff  796  6  2  2019 pubkey.pem

次に、Openssl を通じて pubkey.pem を解析します。

openssl rsa -pubin -text -modulus -in pubkey.pem
Public-Key: (4096 bit)
...
Exponent: 3 (0x3)
Modulus=B0BEE5E3E9...
...

n と e を正常に取得します。ここで、n=Moudles、e=Exponent。

暗号化の式を振り返ってみましょう。

plain_text ^ e ≡ cipher_text (mod n)
其中e、n、cipher_text均是已知的,进行一下简单的格式变换可以得出
plain_text = (kn + (cipher_text mod n)) ^ 1/3

ただし、低暗号化インデックス攻撃の場合、e が小さい場合、ブルート フォース攻撃を実行できることがわかっています。

次に、次のコードを使用してブルート フォース攻撃を実行します。

import os, time,gmpy2

def main():
    start_time = 0
    c_time = 0
    n = 721059527572145959497866070657244746540818298735241721382435892767279354577831824618770455583435147844630635953460258329387406192598509097375098935299515255208445013180388186216473913754107215551156731413550416051385656895153798495423962750773689964815342291306243827028882267935999927349370340823239030087548468521168519725061290069094595524921012137038227208900579645041589141405674545883465785472925889948455146449614776287566375730215127615312001651111977914327170496695481547965108836595145998046638495232893568434202438172004892803105333017726958632541897741726563336871452837359564555756166187509015523771005760534037559648199915268764998183410394036820824721644946933656264441126738697663216138624571035323231711566263476403936148535644088575960271071967700560360448191493328793704136810376879662623765917690163480410089565377528947433177653458111431603202302962218312038109342064899388130688144810901340648989107010954279327738671710906115976561154622625847780945535284376248111949506936128229494332806622251145622565895781480383025403043645862516504771643210000415216199272423542871886181906457361118669629044165861299560814450960273479900717138570739601887771447529543568822851100841225147694940195217298482866496536787241

    k = 0

    c_path = os.getcwd()
    fname = c_path + "/flag.enc"
    print(fname)
    f = open(fname, 'rb')
    c = f.read()
    c_num = int.from_bytes(c, byteorder='big')

    mod_num = c_num % n
    print('n = ' + str(n))
    print('mod = ' + str(mod_num))
    start_time = int(time.time())
    while True:

        c_time = int(time.time())
        time_pass = c_time-start_time
        if (c_time - start_time) == 10:
            print("current k: " + str(k))
            start_time = c_time

        y = k * n + mod_num
        root_num, status = gmpy2.iroot(y,3)

        if status == 1:break
        else:
            k = k + 1
    print('plain_text = ' + str(root_num))

if __name__ == "__main__":
    main()

約 5 分間待つと、ブルート フォースによって取得された平文の情報が表示されますが、これはエンコードされているため、変換する必要があります。


plain_text = 440721643740967258786371951429849843897639673893942371730874939742481383302887786063966117819631425015196093856646526738786745933078032806737504580146717737115929461581126895844008044713461807791172016433647699394456368658396746134702627548155069403689581548233891848149612485605022294307233116137509171389596747894529765156771462793389236431942344003532140158865426896855377113878133478689191912682550117563858186

コードによってプレーンテキスト値を文字に変換します。

def main():
    plain_text = 440721643740967258786371951429849843897639673893942371730874939742481383302887786063966117819631425015196093856646526738786745933078032806737504580146717737115929461581126895844008044713461807791172016433647699394456368658396746134702627548155069403689581548233891848149612485605022294307233116137509171389596747894529765156771462793389236431942344003532140158865426896855377113878133478689191912682550117563858186

    plain_text_in_char = []

    while plain_text != 0:
        plain_text, c = divmod(plain_text, 256)
        plain_text_in_char.append(chr(c))

    plain_text_in_char.reverse()

    print(''.join(plain_text_in_char))

if __name__ == "__main__":
    main()

平文情報の取得に成功しました:

Didn't you know RSA padding is really important? Now you see a non-padding message is so dangerous. And you should notice this in future.Fl4g: PCTF{Sm4ll_3xpon3nt_i5_W3ak}

3. 実際の戦闘シナリオ - 弱いコーディング

文字エンコーディングの基本

エンコードされた文字の豊富なコレクション:

早期标准:ASCII、EBCDIC
西欧标准:ISO-8859-1、ISO-8859-5、ISO-8859-6、ISO-8859-7、ISO-8859-11、ISO-8859-15 等
DOS 字符集:CP437、CP737、CP850 等
Windows 字符集:Windows-1250、Windows-1251、Windows-1252 等
中文字符集:GB2312、GBK 等
Unicode:Unicode、UTF-7、UTF-8、UTF-16、UTF-32 等

一般的なエンコード方法:

  • URL エンコード: 文字の ASCII 値を 2 つの 16 進数で表し、その前にエスケープ文字 % を配置します。次に、非 ASCII 文字を UTF-8 バイト シーケンスに変換してから、エスケープ文字 % を配置します。
  • Base64 エンコード: Base64 は、64 文字でバイナリ データを表す方法です。64 = 2 ^ 6 であるため、6 ビットごとに印刷可能な文字にマッピングできます。また、6 ビットごとがバイトの 4 分の 3 に等しいため、バイトの 4 分の 3 ごとに文字にマッピングされることが簡単に理解できます。新しい言葉の祭り。3で割り切れないバイト数は最後に「0」を付けてエンコードしてください。

事件戦闘

ワイドバイトインジェクション:

PHP を使用して MySQL に接続する場合、「setcharacter_set_client
= gbk」を設定すると、コード変換の問題が発生します。これは、私たちがよく知っているワイドバイト インジェクションです。ワイドバイト インジェクションがある場合、インジェクション パラメータは % で埋められます。 DF%27、食べてもいいよ (%5C)

通常の URL:

http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1

提出するとき:

id=1' and 1=1%23

このとき、mysql によって実行されるステートメントは次のとおりです。

select * from user where  id ='1\' and 1=1#'

明らかに、これは正常に挿入されておらず、送信すると

id=1%df' and 1=1%23

このとき、mysql によって実行されるステートメントは次のとおりです。

select * from user where id ='1運' and 1=1#'

このとき、mysqlは「luck」を認識しないため空として扱うため、正常に注入されます。

4. 実際のシナリオ - 証明書の偽造

証明書に関する基本的な知識

完全にデジタル証明書としても知られる証明書は、公開鍵認証システムに基づく電子文書であり、公開鍵所有者の身元を証明するために使用されます。

証明書には通常、次の情報が含まれます。

  • 公開鍵情報
  • 所有
  • デジタル証明書認証局による証明書のデジタル署名

証明書の発行:

证书的颁发,往往是一个商业行为,需要证书颁发机构CA进行颁发,CA是公钥基础设施的核心,负责签发证书、认证证书、管理证书。

基本颁发流程:服务商想CA提出申请--》CA验证和确认服务商身份,通过审核并将公钥信息与用户身份绑定--》CA 为绑定了身份的公钥进行签名,得到证书。

但是证书颁发机构如果直接面向所有的服务商,无疑工作量巨大,所以为了解决这一问题,往往会引入中间商的概念,中间商只要持有根证书机构签发的中介证书,就有给服务商授权证书的权利。这里服务商获得的证书叫终端实体证书。

証明書の検証:

证书的验证涉及到的是一个信任链的问题,当用户访问一个网站时,浏览器会执行路径验证算法,使用网站提供的证书去对应系统中预安装的根证书,通过验证两者是否匹配来确定证书是否合法。

根证书:证书颁发机构的公钥证书,用于签名客户公钥信息。

リスク生成シナリオ: 信頼チェーンの中断、証明書の信頼チェーンの誤ったバックトラッキング

  • 信頼チェーン内の非ルート ノードの証明書は自己署名されています。
  • 信頼チェーン全体のすべてのノードのチェックが完了しない。
  • 信頼チェーン内のノード証明書に、いくつかの基本情報または追加の重要な拡張情報が不足しています。
  • 信頼チェーンの上位ノードの証明書が無効であるか、攻撃者によって盗まれています。

事件戦闘

中間者攻撃は、飲茶の安全でない証明書が原因で発生します。

ここでは、中間者攻撃におけるプロキシの安全性の確保を分析の例として取り上げます。

まず、SSL ハンドシェイクを必要とする Web サイト、つまり csdn: https://www.csdn.net/ などの https Web サイトを探しましょう。

ここに画像の説明を挿入

通常の状況では、Web サイトの証明書が有効であることがわかります。

現時点では、burpsuite を使用して csdn のトラフィックをキャプチャしています。ブラウザには、リンクがプライベートではないことが表示され、プロキシが存在することが検出されます。

ここに画像の説明を挿入

しかし、Burpsuite 証明書をブラウザにインポートし、プロキシ サーバーを信頼させ、再度パケットをキャプチャしたところ、csdn のトラフィック パケットが正常にキャプチャできたことがわかりました。これは典型的な中間者攻撃です。不完全な証明書検証信頼チェーンに属しているため、問題が発生しました。

ここに画像の説明を挿入

5. 実戦シナリオ - 弱い乱数

弱い乱数の基本概念

乱数とは何ですか?

随机数最基本的概念是统计学意义上的伪随机数,对于给定的一个样本集,每个元素出现的概率是大概相似的,只要从人类的视角看上去一组数是随机的,就符合统计学意义上的伪随机数定义

因为统计学上的伪随机数,在给定随机样本和随机算法的情况下,能够有效地演算出随机样本的剩余部分,因此统计学上的伪随机数需要得到进一步地安全强化,密码学安全的伪随机数应运而生。

擬似乱数の生成:

产生随机数的方法被称为随机数生成器(RNG, random number generator)。
实际应用中我们往往使用伪随机就足够了,这些随机数主要通过一个固定的、可重复的计算方法生成,这些计算方法经过特殊的设计,使得产生的结果具有类似真随机数的统计学特征。这种生成的伪随机数一般只是重复的周期比较大的数列,以算法和种子值共同作用生成

暗号的に安全な擬似乱数:

密码学上安全的伪随机数,往往要求其具有随机数的特征,而不是类似于伪随机数那样的固定随机列表。

疑似乱数のセキュリティを向上させる方法:

一种方法是将账号信息与时间戳进行绑定,作为随机数生成的种子,在时间序列上实现随机。

此外,使用强的伪随机数算法,比如基于数学安全问题的伪随机数。

安全対策

CVE-2019-10908 を例として、オープンソース プロジェクトの修正を見てみましょう。

まず、パッケージの使用に関して、org.apache.commons.lang.RandomStringUtils の使用が中止され、java.security.SecureRandom に置き換えられました。パッケージは暗号的に安全な擬似乱数生成器であり、乱数生成器はアルゴリズム設計の観点から暗号的に安全であり、内部で使用されるシード値の強度はより高くなります。

java.security.SecureRandom 実装コード:

private static final String SYMBOLS = "abcdefghi jklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
private final SecureRandom random = new SecureRandom();
private static final int PASSWORD_LENGTH = 32;
    // ...
    StringBuilder sb = new StringBuilder(PASSWORD_LENGTH);
    for(int i=0; i<PASSWORD_LENGTH; i++) {
    
    
        int index = random.nextInt(SYMBOLS.length());
        sb.append(SYMBOLS.charAt(index));
    }
    String password = sb.toString();

簡単に言えば、開発プロセス中に安全な暗号化ライブラリを通じて CPRNG を使用することです。

6. 実戦シナリオ - ソルトなしのハッシュ

ハッシュとソルトの簡単な理解

ハッシュの適用:

HASH 函数具备很多优秀的特性,比如计算不可逆、难以伪造、数据压缩等,因此它具备很多应用场景。

基于计算不可逆和难以伪造这两个优秀的安全特性,HASH 函数的一个主要应用场景,就是校验数据传递的完整性。将要传递的数据作为 HASH 函数的输入,生成散列值 A;再在接收端将 A 作为 HASH 函数的输入,生成散列值 B。通过对比 A 和 B,就可以快速判断出数据传输的完整性和数据的真实性。

基于此方法,hash往往被用于计算软件、文件的完整性和真实性,另外,在密码存储方面也常常被用于账户密码的安全存储。

「塩」について簡単に紹介

用户账户认证过程,通常涉及到密码的存储,这就是盐的主要应用场景之一。

我们都知道,密码的存储通常是放在数据库中,关于密码的存储形态有很多种,通常可选的方案包括明文、散列值等。

采用明文存储的方案,一旦发生了入侵事件,或者系统存在漏洞使得数据库外泄,就会导致大规模的用户账户外泄,这种安全事件是灾难性的。所以目前大部分系统采取的方案都是存储散列值,在这种情况下,即使是系统也无法得知用户的密码是什么。

但是随着目前彩虹表攻击的日渐流行,以及公开彩虹变的数据愈加庞大,直接使用hash进行数据传输和存储已经不在安全,而加盐则顺势成为了对抗彩虹变攻击的有效方法。

実際の事例

顧客の特​​定のシステムでは、単純な MD5 アルゴリズムを使用してユーザーのログイン パスワードが送信されます。

ここに画像の説明を挿入

中間者攻撃の場合、MD5 以降のパスワードの内容が傍受されると、ハッシュ レインボー テーブルから対応する平文パスワードを簡単に見つけることができます。

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_45590334/article/details/125671435