🔥

最新鋭のLLMモデルによるセキュアコーディング四番勝負 〜脆弱性を見抜くのはキミだ!〜

2024/03/07に公開

最新鋭のLLM(ChatGPT, Gemini, Claude, Llama)に、Githubが公開しているSecure Code Gameで勝負させてみたら、結構いい勝負してくれた、というお話です。
あと単純にめっちゃ勉強になりました。

結果だけ知りたい方はこちら

Secure Code Gameについて

脆弱性を含むコードを実際にデバッグすることでセキュアコーディングが学べるというものです。Season 1 と 2 があり、それぞれLevel 1 ~ 5 まであります。使用言語はpython, go, c, javascript, ymlと複数あるので、誰でも取り組めるようになっています。

リポジトリ:https://github.com/skills/secure-code-game?tab=readme-ov-file

本記事ではSeason 1のLevel1, 2, 3, 5を扱います

Secure Code Gameのはじめかた

環境構築

ローカルにリポジトリをクローンする方法と、Codespaceというクラウド上の実行環境(?)を使う方法があるようです。
Codespacesは使用時間上限があるようなので、ローカルにクローンすることにしました。

  1. READMEのStart courseをクリックでリポジトリが複製されます。
  2. ローカルにクローンします。
  3. python-ldapとやらをインストールする必要があるのですが、READMEにはmacOSのためのコマンド書いていません。

調べると、pipでインストールできるようです。
https://www.python-ldap.org/en/python-ldap-3.3.0/installing.html

以下を実行
pip install python-ldap --global-option=build_ext --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl"

あとはREADMEに従ってインストールを進めます。
pip3 install pyOpenSSL
pip3 install -r requirements.txt

ルール

Level 1 ~ 5 のプロジェクトにはそれぞれ以下のようなファイルがあります。

  • code:レビューの対象となる脆弱なコード
  • hack:コードの脆弱性を突くテスト(実行すると最初は失敗)
  • hint:行き詰まったときのヒント
  • solution:解決策の一例
  • tests:修正後にもパスするべきテスト(最初も成功する)

つまりhackにもtestにもパスするようにcodeを修正せよ、ということですね。

使用するLLMモデルについて

以下のモデルを使用し、ブラウザからプロンプトを与えます

モデル 開発会社 料金
GPT4 OpenAI 月$20
Gemini Advanced Google 初回2ヶ月無料, 月¥2900
Claude 3 Opus Anthropic 月$20
Llama2 70B Meta 無料

Claude3についてはつい先日発表されたモデルであり、ベンチマークテストではGPT4超えの性能を持つとのこと。

プロンプトは以下の文を英語に翻訳したものを与えます

回答の評価方法

回答はDeepLを使用して日本語に直し、脆弱性については、Githubの用意したものを答えていれば +1点(複数ある場合は数に応じて +1点)とし、また提案された解決策により全てのテストにパスすれば +3点とします。

LLMの準備が整ったようなので、さっそく勝負開始!!

記事を読んでいるみなさんも一緒に挑戦してみてください!

Level 1

ソースコード

from collections import namedtuple

Order = namedtuple('Order', 'id, items')
Item = namedtuple('Item', 'type, description, amount, quantity')

def validorder(order: Order):
    net = 0

    for item in order.items:
        if item.type == 'payment':
            net += item.amount
        elif item.type == 'product':
            net -= item.amount * item.quantity
        else:
            return "Invalid item type: %s" % item.type

    if net != 0:
        return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net)
    else:
        return "Order ID: %s - Full payment received!" % order.id

LLMの回答

モデル 指摘した脆弱性 提案した解決策
GPT4 入力検証の欠如
エラー処理と情報漏洩
浮動小数点の使用
入力バリデーションを追加。
四捨五入エラーを避けるため、金額計算に10進数を使用。
情報漏えいを防ぐため、エラーメッセージを標準化。
Gemini Advanced 入力検証の欠落
サービス拒否(DoS)の可能性
入力検証を実装
修正オーダーの最大アイテム数を制限する
Claude 3 Opus 入力検証の欠如
SQLインジェクションの可能性
例外処理の欠如
入力検証を実装
サニタイズ、プリペアドステートメントの使用
適切な例外処理を実装
Llama2 70B 安全でない直接オブジェクト参照(IDOR)
SQL インジェクション
クロスサイトスクリプティング
入力の検証
情報開示
支払いタイプを定義
パラメータ化されたクエリを使用
オブジェクトの型をチェック
機密情報を明らかにしない、単一の一般的なメッセージを使用

意外と回答がばらけて面白い結果となりましたね
IDORとか初めて聞く単語もあります

回答の全文

GPT4

脆弱性と提案

1.入力検証の欠如:

  • 問題:validorder関数は入力オーダーとその項目に対して完全な検証を行わない。不正な入力や予期せぬ入力は予期せぬ動作やエラーを引き起こす可能性がある。
  • 悪用:攻撃者は、予期しない項目型や構造を持つオーダーオブジェクトを渡す可能性があり、 その結果、関数が意図しない動作をしたり、エラーメッセージによって情報が漏れたりする可能性がある。
  • 修正:より強固な入力検証を実装する。処理の前に、order.items の各項目が期待される構造に従い、有効なデータを含んでいることを確認する。

2.エラー処理と情報漏洩

  • 問題:何がうまくいかなかったかについての詳細を含むエラーメッセージを返すと(例えば、"Invalid item type"(無効なアイテムタイプ))、攻撃者にアプリケーションの内部動作についての洞察を与える可能性があります。
  • 悪用:攻撃者は詳細なエラーメッセージを使って攻撃戦略を練ったり、アプリケーションの予期せぬ動作を引き起こす方法を発見したりすることができます。
    対策:エラーメッセージを標準化し、アプリケーションの内部状態やロジックに関する情報を少なくする。デバッグのために、詳細なエラーを内部的に記録する。

3.浮動小数点の使用:

  • 問題:コードでは、浮動小数点演算(item.amount * item.quantity)を使用して金額を計算しています。浮動小数点数は2進数表現であるため丸め誤差が発生する可能性があり、正確な金額計算には適していません。
  • 悪用:これは、セキュリティの脆弱性には直接つながらないかもしれませんが、金銭的な不一致を引き起こし、信用問題や長期的な金銭的損失につながる可能性があります。
  • 修正:丸め誤差を避けるために、貨幣の値には10進数表現(例えば、PythonのDecimal型)を使ってください。

修正コードの例

from collections import namedtuple
from decimal import Decimal

Order = namedtuple('Order', 'id, items')
Item = namedtuple('Item', 'type, description, amount, quantity')

def validorder(order: Order):
    if not isinstance(order, Order) or not all(isinstance(item, Item) for item in order.items):
        return "Invalid order or item structure."

    net = Decimal('0')

    for item in order.items:
        if item.type not in ['payment', 'product']:
            return "Invalid order content."
        
        item_amount = Decimal(str(item.amount))
        if item.type == 'payment':
            net += item_amount
        elif item.type == 'product':
            net -= item_amount * item.quantity

    if net != 0:
        return f"Order ID: {order.id} - Payment imbalance: ${net:0.2f}"
    else:
        return f"Order ID: {order.id} - Full payment received!"

概要

  • 注文とアイテムの入力検証を追加した。
  • 四捨五入エラーを避けるため、金額計算に10進数を使用。
  • 情報漏洩を避けるためにエラーメッセージを標準化。
Gemini Advanced

もちろんだ!開発チームがより安全なアプリケーションを構築できるように、このコードを分解してみましょう。ここでは、潜在的な脆弱性を精査し、どのように改善できるかを説明します:

脆弱性1:入力検証の欠落

  • 問題:validorder()関数はOrderとItemデータ構造内の値を完全に検証しません。具体的には、以下を確認するためのチェックがない:
    • 品目の数量が正である
    • 品目の数量が正である、または許容範囲内である
  • 悪用攻撃者は、潜在的に以下を渡すことができます:
    • 元の商品価値よりも大きな払い戻しを得るための負の数量
    • 過大な金額を入力し、システムに金銭的損失を与える。
  • 修正:validorder()関数内、およびOrderオブジェクトとItemオブジェクトが作成される際に、入力検証を実装する必要がある。
    修正例
def validorder(order: Order):
  net = 0

  for item in order.items:
    if item.type == 'payment':
      if item.amount <= 0:
        return "Invalid payment amount: %s" % item.amount
      net += item.amount
    elif item.type == 'product':
      if item.amount <= 0 or item.quantity <= 0:
        return "Invalid product amount or quantity"
      net -= item.amount * item.quantity
    else:
      return "Invalid item type: %s" % item.type

  if net != 0:
    return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net)
  else:
    return "Order ID: %s - Full payment received!" % order.id

コードは注意してご使用ください。

脆弱性2:サービス拒否(DoS)の可能性

  • 問題: ユーザーが非常に多くのアイテムを含むオーダーを送信した場合、validorder() 関数の処理に長い時間がかかり、過剰なリソースを消費する可能性がある。
  • 悪用攻撃者は、何千ものアイテムを含む注文をアプリケーションに殺到させることで、この問題を悪用し、サービス拒否につながる可能性があります。
  • 修正オーダーの最大アイテム数を制限する。これは、validorder() の最初にチェックを追加することで実装できる。
    修正例
MAX_ORDER_ITEMS = 50  # Set a reasonable limit 

def validorder(order: Order):
  if len(order.items) > MAX_ORDER_ITEMS:
     return "Order exceeds maximum allowed items"

  # ... rest of the validation logic ...

コードは注意してご使用ください。

重要な考慮事項と強化点

  • 堅牢な検証:ポジティブのような基本的なチェックを超えてください。特定の価格帯、ビジネスルールに基づくアイテムタイプの制限などを強制する必要があるかもしれません。
  • エラー処理:無効な入力を知らせるために単に文字列を返すのではなく、例外を使用することを検討し、コードをより整理します。
  • ログとモニタリング:奇妙な数量や値の注文など、疑わしいアクティビティをログに記録する方法を実装する。
  • セキュリティ・ライブラリ:入力をサニタイズし、より厳密な検証を行うために、確立されたセキュリティライブラリの使用を検討する。
    チームの指導

チームを指導する際に、次のことを強調する:

  • 深層防御(Defense in Depth):セキュリティは単一の修正ではなく、防御を重ね、開発のあらゆる段階でセキュリティについて考えることである。
  • 脅威のモデル化:攻撃者がどのようにシステムを破ろうとするかを想定するよう、チームに指導する。
  • 安全なコーディングの実践:今回取り上げたような一般的な脆弱性についての認識を促し、セキュリティに焦点を当てたツールやライブラリの使用を奨励する。
    別のコード・スニペットを分析したり、特定の脆弱性をより詳細に調査したい場合は、私に知らせてください!
Claude 3 Opus

チームの皆さん、こんにちは!セキュリティの専門家として、あなたが提供したコードをレビューし、いくつかの潜在的な脆弱性を特定しました。一つ一つを確認し、リスクを理解し、それらに対処する方法を議論しましょう。

脆弱性1:入力検証の欠如
説明します:
このコードでは、validorder関数に渡されるorderパラメータは常に、期待される構造と型を持つ有効なOrderオブジェクトであると仮定しています。しかし、攻撃者がこの関数への入力を制御することができれば、不正な形式や悪意のあるデータを渡す可能性があり、予期しない動作やセキュリティ上の問題を引き起こす可能性がある。

悪用の例
攻撃者は無効な、あるいは予期しないアイテムタイプを持つ悪意のある Order オブジェクトを作成し、その関数がエラーメッセージを返すように仕向けることができます。

提案
適切な入力検証を実装して、order パラメータとそれに関連する items が期待される型と構造であることを確認してください。予期しない動作を防ぐために、各項目の typeamountquantity を検証する。

固定コードの例

def validorder(order: Order):
    if not isinstance(order, Order):
        return "Invalid order object"

    net = 0
    for item in order.items:
        if not isinstance(item, Item):
            return "Invalid item object"

        if item.type not in ['payment', 'product']:
            return "Invalid item type: %s" % item.type

        if not isinstance(item.amount, (int, float)):
            return "Invalid item amount"

        if item.type == 'product' and not isinstance(item.quantity, int):
            return "Invalid item quantity"

        if item.type == 'payment':
            net += item.amount
        elif item.type == 'product':
            net -= item.amount * item.quantity

    if net != 0:
        return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net)
    else:
        return "Order ID: %s - Full payment received!" % order.id

脆弱性2:SQLインジェクションの可能性
説明
このコードでは order オブジェクトがどのように生成されるのか、あるいはそのデータがどこから来るのかが示されていません。もし order データがユーザー入力から作成され、適切にサニタイズされていない場合、特に order.id やその他のフィールドが SQL クエリで使用されている場合、SQL インジェクション攻撃に対して脆弱になる可能性があります。

悪用の例
攻撃者は、order.id やその他のフィールドに SQL インジェクションのペイロードを含む悪意のある入力を作成し、意図した SQL クエリを変更したり、データベース内の機密データにアクセスしたり操作したりできる可能性があります。

提案
order`オブジェクトを構築するために使用されるユーザ入力は、SQLクエリで使用する前に適切にサニタイズされ、検証されるようにしてください。SQL インジェクションの脆弱性を防ぐために、パラメータ化されたクエリやプリペアドステートメントを使用してください。

修正されたコード例

# Assuming you are using a database library like SQLite3
import sqlite3

def create_order(order_data):
    # Validate and sanitize the order data before creating the Order object
    # ...

    # Create the Order object
    order = Order(order_data['id'], order_data['items'])

    # Store the order in the database using parameterized queries
    conn = sqlite3.connect('orders.db')
    c = conn.cursor()
    c.execute("INSERT INTO orders (id, items) VALUES (?, ?)", (order.id, json.dumps(order.items)))
    conn.commit()
    conn.close()

    return order

脆弱性3:例外処理の欠如
説明
コードには、潜在的なエラーや予期せぬ状況を優雅に処理するための例外処理が含まれていません。もし validorder 関数の実行中に例外が発生すると、処理されない例外が発生し、アプリケーションをクラッシュさせたり、エラーメッセージに含まれる機密情報を漏洩させたりする可能性があります。

提案
潜在的な例外をキャッチして優雅に処理するために、適切な例外処理を実装する。特定の例外をキャッチし、適切なエラーメッセージやフォールバック動作を提供するために try-except ブロックを使用する。

コード例を修正しました:

def validorder(order: Order):
    try:
        # Input validation and order processing code
        # ...

    except Exception as e:
        # Log the exception for debugging purposes
        logger.error("An error occurred while processing the order: %s", str(e))
        return "An unexpected error occurred. Please try again later."

これらは、私が提供されたコードから特定した重要な脆弱性の一部である。セキュリティは継続的なプロセスであり、新しい脆弱性に対処するために定期的にコードを見直し、更新することが重要であることを忘れないでください。

アプリケーションの開発を続ける際には、以下のベストプラクティスに留意してください:
1.潜在的な攻撃を防ぐために、常にユーザ入力を検証し、サニタイズすること。
2.SQL インジェクションの脆弱性を防ぐために、データベースとやりとりするときには、 パラメータ化されたクエリーやプリペアド・ステートメントを使用する。
3.エラーを優雅に処理し、情報漏えいを防ぐために、適切な例外処理を実装する。
4.依存関係やライブラリを最新の状態に保ち、最新のセキュリティパッチを確実に適用する。
5.セキュリティ監査と侵入テストを定期的に実施し、脆弱性を特定して対処する。

さらに質問がある場合、あるいはアプリケーションのセキュリティ確保について支援が必要な場合は、遠慮なく連絡してください。堅牢で安全なシステムの構築をお手伝いします。

安全で信頼できるアプリケーションを一緒に作りましょう!

Llama2 70B

コードを見直した結果、対処すべきセキュリティの脆弱性をいくつか特定しました。それぞれの脆弱性とその影響について説明し、問題を修正するためのコードの改善案を提示します。
1.安全でない直接オブジェクト参照(IDOR):
validorder関数はハードコードされた文字列'payment'を使って、アイテムが支払いであるかどうかをチェックします。これは安全でない直接オブジェクト参照(IDOR)の脆弱性です。攻撃者は、order.items リストを操作し、'payment' のタイプを持つカスタムアイテムを追加することで、支払いの検証をバイパスすることができます。
提案ハードコードされた文字列を使用する代わりに、定数または設定変数を使用して支払いタイプを定義してください。そうすることで、コードがより安全になり、メンテナンスも簡単になります。
コードを修正しました:

from collections import namedtuple
Order = namedtuple('Order', 'id, items')
Item = namedtuple('Item', 'type, description, amount, quantity')
PaymentType = 'payment'
def validorder(order: Order):
    net = 0
    for item in order.items:
    if item.type == PaymentType:
        net += item.amount
    elif item.type == 'product':
        net -= item.amount * item.quantity
    else:
        return "Invalid item type: %s" % item.type
    if net != 0:
        return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net)
    else:
        return "Order ID: %s - Full payment received!" % order.id

2.SQL インジェクション:
validorder`関数はデータベースからデータを取得するために生のSQLクエリを使用します。これは SQL インジェクション攻撃に対して脆弱です。攻撃者は悪意のある SQL コードを注入することでこれを悪用し、機密データを取り出したり、データベースを変更したりすることができます。
提案SQLインジェクション攻撃を防ぐには、パラメータ化されたクエリを使用してください。パラメータ化されたクエリでは、SQLコードに直接データを含めるのではなく、クエリにパラメータとしてデータを渡すことができます。これにより、悪意のあるデータがクエリに注入されるのを防ぐことができます。
Fixed code:

from collections import namedtuple
Order = namedtuple('Order', 'id, items')
Item = namedtuple('Item', 'type, description, amount, quantity')
def validorder(order: Order):
    net = 0
    for item in order.items:
    if item.type == 'payment':
        net += item.amount
    elif item.type == 'product':
        net -= item.amount * item.quantity
    else:
        return "Invalid item type: %s" % item.type
    query = "SELECT * FROM orders WHERE id = ?"
    order_data = db.execute(query, (order.id,))
    if order_data:
        net = order_data[0].net
        return "Order ID: %s - Full payment received!" % order.id
    else:
        return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net)

3.クロスサイトスクリプティング(XSS):
validorder関数は注文 ID と支払金額を含むメッセージを返します。このメッセージはクロスサイトスクリプティング(XSS)攻撃に対して脆弱です。攻撃者は悪意のある JavaScript コードを注入することでこれを悪用し、機密データを盗んだり、ユーザのセッションを制御したりすることができます。
提案
ここで回答が途切れてしまったためcontinueと入力

続けて、validorder関数のセキュリティーを向上させるための追加提案をいくつか挙げます:
4.入力の検証:
validorder関数は入力データを適切に検証しない。これは、order オブジェクトが有効な type 属性を持つ Item オブジェクトのみを含んでいると仮定している。しかし、攻撃者は order オブジェクトを操作したり、悪意のあるデータをシステムに注入したりすることで、これを悪用することができる。
提案:orderオブジェクトと各Itemオブジェクトの型をチェックし、それらが有効な属性を持っていることを確認することで、入力データを適切に検証する。
修正したコード

from collections import namedtuple
Order = namedtuple('Order', 'id, items')
Item = namedtuple('Item', 'type, description, amount, quantity')
def validorder(order: Order):
    if not isinstance(order, Order):
        return "Invalid order object"
    net = 0
    for item in order.items:
        if not isinstance(item, Item):
            return "Invalid item object"
        if item.type not in ['payment', 'product']:
            return "Invalid item type: %s" % item.type
        if item.amount < 0 or item.quantity < 0:
            return "Invalid amount or quantity"
        net += item.amount * item.quantity
        if net < 0:
            return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net)
        else:
            return "Order ID: %s - Full payment received!" % order.id

5.情報開示:
validorder`関数は支払い状況に応じて異なるメッセージを返す。これはセキュリティ上の問題ではないように見えるかもしれませんが、攻撃者がシステムに関する情報を収集するために悪用することができます。
提案機密情報を明らかにしない、単一の一般的なメッセージを使用する。こうすることで、攻撃者がシステムの支払い状況に関する情報を推測したり、システムを何らかの方法で悪用したりすることを防ぐことができます。
修正されたコード

from collections import namedtuple
Order = namedtuple('Order', 'id, items')
Item = namedtuple('Item', 'type, description, amount, quantity')
    def validorder(order: Order):
    net = 0
    for item in order.items:
        net += item.amount * item.quantity
        if net < 0:
            return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net)
        else:
            return "Order ID: %s - Payment received!" % order.id

これらの提案を実装することで、validorder関数はより安全になり、攻撃を受けにくくなります。常に入力データを検証し、安全なコーディングを行い、ユーザーに返す情報の量を制限することを忘れないでください。

それでは正解の発表です

正解

  1. 浮動小数点アンダーフローの脆弱性: 浮動小数点データ型を財務値を含む計算に使用すると、予期せぬ丸め誤差や比較誤差が生じます。例えば、Pythonインタープリターで0.1 + 0.2を実行すると、0.3ではなく0.300000000000004となります。これを解決するには、"学校で習う算数と同じように "動作する計算に Decimal 型を使うことです。また、Decimalコンストラクタに渡す前に、浮動小数点値を文字列に変換する必要があります。浮動小数点値を Decimal コンストラクタに渡すと、丸めた値が代わりに格納されます。
  2. 入力された値の制限: 攻撃者が膨大な数のアイテムを送信し、その結果ネットが膨大になるというシナリオからも保護する必要がある。すべての変数を妥当な値に制限することで、これを実現できる。

ということで、脆弱性の指摘では、GPT4が+2点、他は+1点となります👏👏
とはいっても、これはGithubが用意した答えなので、他の回答も同等に(もしくはそれ以上に)重要なものです。

GPT4は脆弱性については正解でしたが、テストにはパスしませんでした
原因は、item.quantityの型チェックを怠っていたことです。入力検証をするとはいっていたけど、細かいところがまだ甘いようです

ということで、解決策の提案では、全員0点でした😣

暫定での得点は以下のようになります

Level1 Level2 Level3 Level5 総合得点
GPT4 脆弱性: 2
解決策: 0
脆弱性:
解決策:
脆弱性:
解決策:
脆弱性:
解決策:
2
Gemini Advanced 脆弱性: 1
解決策: 0
脆弱性:
解決策:
脆弱性:
解決策:
脆弱性:
解決策:
1
Claude 3 Opus 脆弱性: 1
解決策: 0
脆弱性:
解決策:
脆弱性:
解決策:
脆弱性:
解決策:
1
Llama2 70B 脆弱性: 1
解決策: 0
脆弱性:
解決策:
脆弱性:
解決策:
脆弱性:
解決策:
1

まだ横ばいですね〜
やはり一発で正解を導くのは最新鋭LLMでも厳しいのか!?

解説

解決策の説明
浮動小数点アンダーフローの脆弱性。
hack.pyにおいて、攻撃者は偽の支払いとして極めて高額な金額を供給し、その直後に支払いを取り消すことでシステムを騙す。このエクスプロイトでは巨大な数値が渡されるため、購入した商品の代金を差し引く際にアンダーフローが発生し、結果として正味ゼロになってしまう。
どのような値でも受け入れるのではなく、システム入力を許容範囲に制限するのは良い習慣だ。
また、攻撃者が膨大な数のアイテムを送信し、その結果ネットが膨大になるというシナリオからも保護する必要がある。すべての変数を妥当な値に制限することで、これを実現できる。
加えて、浮動小数点データ型を財務値を含む計算に使用すると、予期せぬ丸め誤差や比較誤差が生じます。
例えば、Pythonインタープリターで0.1 + 0.2を実行すると、0.3ではなく0.300000000000004となります。
れを解決するには、"学校で習う算数と同じように "動作する計算に Decimal 型を使うことです。詳しくはPythonのDecimalに関する公式ドキュメントを読んでください: (https://docs.python.org/3/library/decimal.html)。
また、Decimalコンストラクタに渡す前に、浮動小数点値を文字列に変換する必要があります。浮動小数点値を Decimal コンストラクタに渡すと、丸めた値が代わりに格納されます。
インタープリターの以下の例を見比べてみてください:>> Decimal(0.3) Decimal('0.299999999988897769753748434595763683319091796875') >> Decimal('0.3') Decimal('0.3')
入力検証は、許容される値の範囲をテストするだけでなく、データ型もチェックするように拡張されるべきである。非整数の数量を使用することによって引き起こされるこの特定のバグは、要件エンジニアリングへの注意が不十分であるために発生する可能性があります。ある文脈では、整数でない量の商品を購入することは許容されますが(例えば、端数の株を購入する)、私たちのオンラインショップの文脈では、悪意なく、正の整数のみの商品を購入するユーザーを誤って信頼してしまいました。

from collections import namedtuple
from decimal import Decimal

Order = namedtuple('Order', 'id, items')
Item = namedtuple('Item', 'type, description, amount, quantity')

MAX_ITEM_AMOUNT = 100000 # maximum price of item in the shop
MAX_QUANTITY = 100 # maximum quantity of an item in the shop
MIN_QUANTITY = 0 # minimum quantity of an item in the shop
MAX_TOTAL = 1e6 # maximum total amount accepted for an order

def validorder(order):
    payments = Decimal('0')
    expenses = Decimal('0')

    for item in order.items:
        if item.type == 'payment':
            # Sets a reasonable min & max value for the invoice amounts
            if -MAX_ITEM_AMOUNT <= item.amount <= MAX_ITEM_AMOUNT:
                payments += Decimal(str(item.amount))
        elif item.type == 'product':
            if type(item.quantity) is int and MIN_QUANTITY < item.quantity <= MAX_QUANTITY and MIN_QUANTITY < item.amount <= MAX_ITEM_AMOUNT:
                expenses += Decimal(str(item.amount)) * item.quantity
        else:
            return "Invalid item type: %s" % item.type
    
    if abs(payments) > MAX_TOTAL or expenses > MAX_TOTAL:
        return "Total amount payable for an order exceeded"

    if payments != expenses:
        return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, payments - expenses)
    else:
        return "Order ID: %s - Full payment received!" % order.id

続いて第2問!!

Level 2

C言語になります
ちなみに私は一ミリもわかりません😇

ソースコード

#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_USERNAME_LEN 39
#define SETTINGS_COUNT 10
#define MAX_USERS 100
#define INVALID_USER_ID -1

// For simplicity, both the private (implementation specific) and the public (API) parts 
// of this application have been combined inside this header file. In the real-world, it
// is expected for the public (API) parts only to be presented here. Therefore, for the 
// purpose of this level, please assume that the private (implementation specific) sections 
// of this file, would not be known to the non-privileged users of this application

// Internal counter of user accounts
int userid_next = 0;

// The following structure is implementation-speicific and it's supposed to be unknown 
// to non-privileged users
typedef struct {
    bool isAdmin;
    long userid;
    char username[MAX_USERNAME_LEN + 1];
    long setting[SETTINGS_COUNT];
} user_account;

// Simulates an internal store of active user accounts
user_account *accounts[MAX_USERS];

// The signatures of the following four functions together with the previously introduced 
// constants (see #DEFINEs) constitute the API of this module

// Creates a new user account and returns it's unique identifier
int create_user_account(bool isAdmin, const char *username) {
    if (userid_next >= MAX_USERS) {
        fprintf(stderr, "the maximum number of users have been exceeded");
        return INVALID_USER_ID;
    }    

    user_account *ua;
    if (strlen(username) > MAX_USERNAME_LEN) {
        fprintf(stderr, "the username is too long");
        return INVALID_USER_ID;
    }    
    ua = malloc(sizeof (user_account));
    if (ua == NULL) {
        fprintf(stderr, "malloc failed to allocate memory");
        return INVALID_USER_ID;
    }
    ua->isAdmin = isAdmin;
    ua->userid = userid_next++;
    strcpy(ua->username, username);
    memset(&ua->setting, 0, sizeof ua->setting);
    accounts[userid_next] = ua;
    return userid_next++;
}

// Updates the matching setting for the specified user and returns the status of the operation
// A setting is some arbitrary string associated with an index as a key
bool update_setting(int user_id, const char *index, const char *value) {
    if (user_id < 0 || user_id >= MAX_USERS)
        return false;

    char *endptr;
    long i, v;
    i = strtol(index, &endptr, 10);
    if (*endptr)
        return false;

    v = strtol(value, &endptr, 10);
    if (*endptr || i >= SETTINGS_COUNT)
        return false;
    accounts[user_id]->setting[i] = v;
    return true;
}

// Returns whether the specified user is an admin
bool is_admin(int user_id) {
    if (user_id < 0 || user_id >= MAX_USERS) {
        fprintf(stderr, "invalid user id");
        return false;
    }    
    return accounts[user_id]->isAdmin;
}

// Returns the username of the specified user
const char* username(int user_id) {
    // Returns an error for invalid user ids
    if (user_id < 0 || user_id >= MAX_USERS) {
        fprintf(stderr, "invalid user id");
        return NULL;
    }    
    return accounts[user_id]->username;
}

LLMの回答

モデル 指摘した脆弱性 提案した解決策
GPT4 バッファオーバフロー
不正な配列インデックス作成
初期化されていないメモリの使用
メモリリーク防止
strcpy の代わりに strncpy を使用
正しいインデックスを使用
NULLチェック
メモリーを解放
Gemini Advanced バッファオーバフロー
配列インデックスの未チェック
メモリ割り当てチェック不足
実施内容の直接公開
strncpy()を使用
インデックスのチェック
メモリ割り当て失敗後にはexitする
内部実装の詳細を別の実装ファイルに移す
Claude 3 Opus バッファオーバフロー
整数オーバーフロー
入力バリデーションの欠如
エラー処理の欠如
境界チェックの欠落
strcpy の代わりに strncpy を使用
値のチェック
パラメータに適切な入力検証を追加
失敗した場合は早めにリターン
境界チェックを変更
Llama2 70B 安全でない直接オブジェクト参照(IDOR)
SQLインジェクション
機密データの安全でない格納
安全でないユーザー認証
機密データの安全でない返却
一意のユーザーIDを生成するために安全な乱数生成器を使用
strtoulstrtol などの安全な関数を使用
暗号化や安全なデータ構造を使用
単純なブール値のフラグではなく安全な認証メカニズムを実装
安全なコピーへのポインターを返す

Llama2くんは独特な観点をお持ちのようですね

回答の全文

GPT4

提供されたコードのセキュリティ脆弱性を分析した結果、悪意のあるユーザーに悪用される可能性のあるいくつかの問題を特定しました。以下では、それぞれの脆弱性について、なぜそれが問題なのか、どのように悪用される可能性があるのかを説明し、修正されたコードの例とともに、コードの改善案を提示する。

脆弱性 1: create_user_account におけるバッファオーバフロー

問題: strcpy 関数は、入力された username の長さが user_account 構造体の username のバッファサイズを超えるかどうかをチェックせずに使用される。これはバッファオーバーフローにつながる可能性がある。

**攻撃者は MAX_USERNAME_LEN よりも長いユーザー名を入力することができ、バッファの終端を越えてプログラムを書き込む可能性がある。これにより、隣接するメモリを上書きし、任意のコードの実行やクラッシュを引き起こす可能性がある。

修正: ユーザー名のコピーには strcpy の代わりに strncpy を使用し、バッファのサイズ以上の文字がコピーされないようにする。

修正例:

strncpy(ua->username, username, MAX_USERNAME_LEN);
ua->username[MAX_USERNAME_LEN] = '\0'; // Ensure null termination

脆弱性 2: create_user_account における不正な配列インデックス作成

問題: accounts[userid_next] = ua; 行は userid_next がインクリメントされた後に使用されるため、ユーザアカウントが間違ったインデックスに保存されるという一つ違いエラーが発生し、accounts 配列の外側のメモリが上書きされる可能性があります。

**また、accounts 配列の外側にあるメモリを上書きする可能性があります。

Fix: accounts 配列に新しいユーザーアカウントを格納する際には、正しいインデックスを使用してください。

修正例:

accounts[ua->userid] = ua; // Use ua->userid instead of userid_next

脆弱性 3: update_setting における初期化されていないメモリの使用

問題: この関数は accounts[user_id] を再参照する前に、その accounts[user_id] が有効なポインタであるかどうかをチェックしません。与えられた user_id に対して create_user_account が呼び出されていない場合、NULL ポインタまたは初期化されていないメモリを再参照してしまう可能性がある。

**攻撃者は、初期化されていないユーザーIDを渡すことでプログラムをクラッシュさせ、サービス拒否を引き起こす可能性があります。

修正: accounts[user_id]にアクセスする前に、accounts[user_id]がNULLでないことを確認するチェックを追加しました。

修正例:

if (accounts[user_id] == NULL)
    return false;

全般的な改善:メモリリーク防止

問題:* ユーザーアカウントに割り当てられたメモリを解放しないため、メモリリークが発生する。

修正: ユーザー・アカウントが不要になったときに割り当て済みメモリーを解放する関数を実装する。

修正例:

void delete_user_account(int user_id) {
    if (user_id >= 0 && user_id < MAX_USERS && accounts[user_id] != NULL) {
        free(accounts[user_id]);
        accounts[user_id] = NULL;
    }
}

結論

これらの脆弱性に対処することは、アプリケーションのセキュリティと安定性を大幅に改善します。バッファ・オーバーフロー、メモリ破壊、メモリ・リークのような一般的なセキュリティ問題を防ぐために、常に入力を検証し、サニタイズし、適切にメモリを管理し、配列インデックスが正しく使用されるようにすることが極めて重要です。

Gemini Advanced

もちろんです!このコードを分析し、あなたのチームをセキュリティー改善に導こう。

脆弱性と推奨事項

1.strcpy()におけるバッファオーバフローの可能性

  • 問題:行 strcpy(ua->username, username); は典型的なバッファオーバーフローの脆弱性です。ユーザが指定した username が許容される最大長 (MAX_USERNAME_LEN) より長い場合、 strcpy()ua->username 配列の境界を超えてデータを書き込み続け、隣接するメモリを上書きしてしまう。

  • 攻撃者は、実行可能なコードを含む悪意のある username を作成することができ、オーバーフローが重要なメモリ領域を上書きした場合、プログラムを制御できる可能性があります。

  • 改良されたコード:

strncpy(ua->username, username, MAX_USERNAME_LEN); 
ua->username[MAX_USERNAME_LEN] = '\0'; // Ensure null termination

strncpy()を使用して、コピーする文字数を制限し、ヌル・ターミネー ターを明示的に追加する。

2.update_setting()`における配列インデックスの未チェック。

  • 問題: strtol() を使用した index から long への変換は、 setting 配列の有効範囲内 (0 から SETTINGS_COUNT - 1) であることを確認するための十分な検証を行わずに行われる。

  • 悪意のあるユーザが境界外のインデックスを提供することで、意図した配列の外側にデータを書き込んでメモリ破壊を引き起こす可能性があります。

改善されたコード:

i = strtol(index, &endptr, 10);
if (*endptr || i < 0 || i >= SETTINGS_COUNT)  // Add check for i 
    return false;

変換後のインデックスが setting 配列の有効範囲内にあることを確認する。

3.create_user_account()`におけるメモリ割り当てチェック不足

  • 問題: malloc()NULL を返すかどうかをチェックしているが、割り当てに失敗してもすぐに終了しない。その代わり、コードは続行されます。NULLの可能性があるポインタを後で使用すると、未定義の動作につながり、プログラムがクラッシュする可能性があります。

  • メモリ割り当てが繰り返し失敗すると、システムリソースを使い果たすサービス拒否攻撃 に利用される可能性があります。

改良されたコード:

ua = malloc(sizeof (user_account));
if (ua == NULL) {
    fprintf(stderr, "malloc failed to allocate memory");
    return INVALID_USER_ID;  // Immediately exit on failure
}

4.実施内容の直接公開

  • 問題: user_account 構造体全体とその内部レイアウトがヘッダーファイルで公開されている。これは潜在的な攻撃者に、悪用する際に特定のメモリ位置を標的にするための貴重な情報を提供します。

改良されたコード:

公開API専用の新しいヘッダーファイルを作成する:

// public_api.h
#define MAX_USERS 100
#define INVALID_USER_ID -1

int create_user_account(bool isAdmin, const char *username);
bool update_setting(int user_id, const char *index, const char *value);
bool is_admin(int user_id);
const char* username(int user_id);

user_account構造体やその他の内部実装の詳細を別の実装ファイルに移す。

一般的なセキュリティに関する推奨事項

  • 入力の検証: 常に、すべてのユーザ提供入力を検証してください。正しい長さ、データタイプ、潜在的な悪意のあるパターンをチェックする。
  • 最小特権: 必要最小限の特権で動作するようにシステムを設計してください。ユーザーの役割が管理者権限を必要としないのであれば、提供しないこと。
  • メモリを直接操作する場合には、極めて慎重であること。プログラミング言語が提供する、より安全なメモリ管理ライブラリの利用を検討しましょう。

チームを指導する

メンタリングセッションでこれらの概念の重要性を強調し、開発者にリスクと適切な緩和手法を理解させましょう。

特定の脆弱性について、より詳細な説明が必要な場合は、私に知らせてください!

Claude 3 Opus

素晴らしい!コードを分析して、潜在的なセキュリティの脆弱性を特定しましょう。それぞれの問題点を調べ、なぜそれが問題なのかを説明し、改善のための提案をします。

1.create_user_account`関数におけるバッファオーバフロー:

  • 問題: strcpy 関数を使用して、適切な境界チェックを行わずに usernameua->username バッファにコピーしている。usernameMAX_USERNAME_LEN` より長い場合、バッファがオーバーフローし、メモリ破壊や任意のコードの実行を引き起こす可能性がある。
  • 悪用攻撃者は MAX_USERNAME_LEN を超える長い username を指定することで、バッファオーバーフローを引き起こすことができる。
  • 修正修正: strcpy の代わりに strncpy を使用し、転送先バッファが常にヌル終端であるようにする。

コードを修正した:

strncpy(ua->username, username, MAX_USERNAME_LEN);
ua->username[MAX_USERNAME_LEN] = '\0';

2.関数 create_user_account における整数オーバーフロー:

  • 問題: userid_next 変数が整数オーバーフローをチェックせずにインクリメントされる。userid_nextが最大値に達し、さらにインクリメントされた場合、負の値に折り返され、予期せぬ動作やセキュリティ問題につながる可能性がある。
  • 悪用攻撃者は多数のユーザーアカウントを作成することで、整数オーバーフローを引き起こし、MAX_USERSの制限をバイパスできる可能性がある。
  • 修正userid_nextをインクリメントする前に、userid_nextMAX_USERS - 1を超えないことを確認するチェックを追加する。

コードを修正した:

if (userid_next >= MAX_USERS - 1) {
    fprintf(stderr, "the maximum number of users have been exceeded");
    return INVALID_USER_ID;
}
ua->userid = userid_next;
accounts[userid_next] = ua;
userid_next++;

3.update_setting` 関数の入力バリデーションの欠如:

  • 問題: update_setting 関数は user_id パラメータを適切に検証しない。このため、負の値や MAX_USERS 以上の値を許してしまい、境界外アクセスやメモリ破壊の可能性がある。
  • 悪用攻撃者は無効な user_id を指定することで、他のユーザの設定にアクセスしたり変更したり、アプリケーションをクラッシュさせたりすることができる。
  • 修正user_id` パラメータに適切な入力検証を追加する。

コードを修正した:

if (user_id < 0 || user_id >= userid_next) {
    return false;
}

4.update_setting` 関数のエラー処理の欠如:

  • 問題点: update_setting 関数は index または value の変換に失敗すると false を返すが、エラーを潔く処理しない。エラーはそのまま実行され、予期しない動作を引き起こす可能性がある。
  • 修正適切なエラー処理を追加し、変換に失敗した場合は早めにリターンするようにした。

コードを修正した:

if (*endptr) {
    fprintf(stderr, "invalid index");
    return false;
}
// ...
if (*endptr) {
    fprintf(stderr, "invalid value");
    return false;
}

5.is_adminusername` 関数の境界チェックの欠落:

  • 問題: is_adminusername 関数は user_id パラメータの境界チェックを行うが、MAX_USERS - 1 までの値を許可する。しかし、ユーザ ID の有効範囲は 0 から userid_next - 1 までである。
  • 悪用攻撃者は MAX_USERS の制限内ではあるが、作成されたユーザーアカウントの有効範囲外の user_id を指定することができ、初期化されていないメモリや無効なメモリにアクセスする可能性がある。
  • 修正MAX_USERSの代わりにuserid_next` を使用するように境界チェックを変更する。

コードを修正した:

if (user_id < 0 || user_id >= userid_next) {
    fprintf(stderr, "invalid user id");
    return false;
}

これらは、提供されたコードで特定された主要なセキュリティ脆弱性の一部です。これらの問題に対処し、提案された修正を実装することで、アプリケーションのセキュリティを改善することができます。

常にユーザ入力を検証し、適切な境界チェックを行い、エラーを優雅に扱い、メモリ割り当てと文字列操作を扱うときには用心することを忘れないでください。

これは網羅的なリストではなく、アプリケーションの具体的な使用方法とコンテキストによって、他の潜在的な脆弱性があるかもしれないことに注意することが重要です。

Llama2 70B

セキュリティの専門家として、私は提供されたコードを分析し、攻撃者に悪用される可能性のあるいくつかの脆弱性を発見しました。以下は、私の発見と改善のための推奨事項です:
1.安全でない直接オブジェクト参照(IDOR):
create_user_account関数は、一意なユーザIDを生成するために単純なインクリメンタルカウンタ(userid_next)を使用しています。これはIDORの脆弱性につながる可能性があり、攻撃者は次のユーザIDを予測し、既知のIDで新しいユーザアカウントを作成することでシステムを操作することができます。
推奨事項一意のユーザーIDを生成するために安全な乱数生成器を使用し、ハッシュテーブルやツリーベースのデータ構造などの安全なデータ構造に格納する。
2.SQL インジェクション:
SQLインジェクション: update_setting 関数は strtol を使って文字列を長い整数に変換します。攻撃者は、NULL 文字を含む文字列のような悪意のある入力を注入することで、これを悪用し、予期しない動作を引き起こす可能性があります。
推奨文字列を符号なし長整数に変換するには strtoulstrtol などの安全な関数を使用し、入力が期待される形式を満たしているか検証すること。
3.機密データの安全でない格納:
user_account構造体は、ユーザー名や設定などの機密データをプレーンテキストで保存する。これは、攻撃者がシステムにアクセスした場合、セキュリティの脆弱性につながる可能性がある。
推奨暗号化や安全なデータ構造を使用するなどして、機密データを安全に保管し、アクセス制御メカニズムを実装して、許可されたユーザーにアクセスを制限することを検討してください。
4.安全でないユーザー認証:
username 関数は、ユーザが管理者であるかどうかを示すために単純なブール値のフラグを使用します。これは認証の脆弱性につながる可能性があり、攻撃者はこのフラグを変更することでシステムを操作することができる。
推奨安全なパスワードの保存や認証プロトコルの使用など、安全な認証メカニズムを実装し、許可されたユーザーだけがシステムにアクセスできるようにする。
5.機密データの安全でない返却:
username関数はユーザ名を含む静的バッファへのポインタを返します。これは、攻撃者がシステムを操作するためにバッファオーバーフローを悪用した場合、セキュリティ脆弱性につながる可能性があります。
推奨strdupのような安全な関数を使用して、ユーザー名の安全なコピーを作成し、安全なコピーへのポインターを返します。
これらの脆弱性に対処するために、コードをどのように改善できるかの例を示します:

#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_USERNAME_LEN 39
#define SETTINGS_COUNT 10
#define MAX_USERS 100
#define INVALID_USER_ID -1
// For simplicity, both the private (implementation specific) and the public (API) parts
// of this application have been combined inside this header file. In the real-world, it
// is expected for the public (API) parts only to be presented here. Therefore, for the
// purpose of this level, please assume that the private (implementation specific) sections
// of this file, would not be known to the non-privileged users of this application
// Internal counter of user accounts
int userid_next = 0;
// The following structure is implementation-speicific and it's supposed to be unknown

ここで途切れたので、continueと入力


// The following structure is implementation-specific and it's supposed to be unknown to non-privileged users
typedef struct {
bool isAdmin;
long userid;
char username[MAX_USERNAME_LEN + 1];
long setting[SETTINGS_COUNT];
} user_account;
// Simulates an internal store of active user accounts
user_account *accounts[MAX_USERS];
// The signatures of the following four functions together with the previously introduced
// constants (see #DEFINEs) constitute the API of this module
// Creates a new user account and returns it's unique identifier
int create_user_account(bool isAdmin, const char *username) {
if (userid_next >= MAX_USERS) {
fprintf(stderr, "the maximum number of users have been exceeded");
return INVALID_USER_ID;
}
// Generate a secure random user ID
user_account *ua = malloc(sizeof(user_account));
if (ua == NULL) {
fprintf(stderr, "malloc failed to allocate memory");
return INVALID_USER_ID;
}
ua->isAdmin = isAdmin;
ua->userid = userid_next++;
strcpy(ua->username, username);
memset(&ua->setting, 0, sizeof(ua->setting));
accounts[userid_next] = ua;
return userid_next++;
}
// Updates the matching setting for the specified user and returns the status of the operation
// A setting is some arbitrary string associated with an index as a key
bool update_setting(int user_id, const char *index, const char *value) {
if (user_id < 0 || user_id >= MAX_USERS)
return false;
// Validate the input parameters
if (strlen(index) > MAX_USERNAME_LEN) {
fprintf(stderr, "the index is too long");
return false;
}
if (strlen(value) > MAX_USERNAME_LEN) {
fprintf(stderr, "the value is too long");
return false;
}
// Convert the index to a long integer
long i = strtol(index, &endptr, 10);
if (*endptr)
return false;
// Check if the index is valid
if (i >= SETTINGS_COUNT)
return false;
// Update the setting
accounts[user_id]->setting[i] = strtol(value, &endptr, 10);
return true;
}
// Returns whether the specified user is an admin
bool is_admin(int user_id) {
if (user_id < 0 || user_id >= MAX_USERS) {
fprintf(stderr, "invalid user id");
return false;
}
return accounts[user_id]->isAdmin;
}
// Returns the username of the specified user
const char* username(int user_id) {
// Returns an error for invalid user IDs
if (user_id < 0 || user_id >= MAX_USERS) {
fprintf(stderr, "invalid user id");
return NULL;
}

ここで途切れたので、continueと入力


// Return a secure copy of the username
char *username_copy = strdup(accounts[user_id]->username);
return username_copy;
}

あなたのコードのセキュリティ脆弱性を特定し、修正する一助となれば幸いです。何か質問があったり、さらなる支援が必要であれば、私に知らせてください。

それでは正解発表です!

正解

  1. security through obscurity(なんか難しそうな概念なので説明は省きます。詳細)
  2. バッファオーバーフローの脆弱性
    hack.cでは、コードが負のインデックス値をチェックしていないことを悪用して、攻撃者が特権をエスカレートさせ、管理者になりました。
    ここでの負のインデックスはメモリへの不正な書き込みを引き起こし、isAdminに影響を与え、管理者でないユーザを管理者に変えてしまいます。

そもそも自分が理解できていないんですが、1はGoogle Advancedが言っている「実施内容の直接公開」というのに該当するのかな?
バッファオーバーフローについてはLlama2以外は言及しているので、とりあえず正解とします(ちょっと雑)

各LLMの提案した解決策を実行した結果、以下のようになりました

  • GPT4:テスト実行時にsegmentation faultというエラーがでる😫
  • Gemini Advanced: 全てのテストにパス👏
  • Claude 3 Opus: テストは全てパスしたけど。変なメッセージが表示される🤔
  • Llama2: テスト実行時にuse of undeclared identifierというエラーがでる😫

なかなか評価が難しいですが、全てパスしたGemini Advanced以外は0点です

正解を見ると、i < 0を判定するだけなのに、意外と見抜けないものなんですね。

暫定での得点は以下のようになります

Level1 Level2 Level3 Level5 総合得点
GPT4 脆弱性: 2
解決策: 0
脆弱性: 1
解決策: 0
脆弱性:
解決策:
脆弱性:
解決策:
3
Gemini Advanced 脆弱性: 1
解決策: 0
脆弱性: 2
解決策: 3
脆弱性:
解決策:
脆弱性:
解決策:
6
Claude 3 Opus 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性:
解決策:
脆弱性:
解決策:
2
Llama2 70B 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性:
解決策:
脆弱性:
解決策:
2

おおっと、ここでGemini Advancedが先頭に躍り出たァーッ!

解説

このコードには2つの脆弱性がある:

1. 脆弱性を悪用したセキュリティの確保

曖昧さによるセキュリティ(STO)の概念は、何か(たとえ脆弱性であっても!)が秘密か隠されていれば、シス テムは安全であり続けることができるという考え方に依拠しています。攻撃者が弱点を知らなければ、それを悪用することはできません。裏を返せば、いったんその脆弱性が露呈すれば、もはや安全ではないということだ。不明瞭さによるセキュリティは、それだけでは効果のないセキュリティ対策であり、以下の理由から避けるべきだと広く信じられている。
単一障害点(single point of failure)が発生する可能性があり、安心感が損なわれる。
code.hでは、user_account構造体は実装の詳細であり、ユーザーには見えないことになっている。
実装の詳細であり、ユーザーには見えないようになっています。そうでなければ、攻撃者は簡単にこの構造体を変更し、「isAdmin」フラグを「true」に変更して管理者権限を得ることができる。
したがって、この例が示すように、不明瞭さによる安全性だけではシステムの安全性は確保できない。攻撃者はコードをリバース・エンジニアリングして脆弱性を見つけることができる立場にある。これはhack.c(下記参照)で暴露されている。
曖昧さによるセキュリティの概念については、こちらをご覧ください:
https://securitytrails.com/blog/security-through-obscurity

2. バッファオーバーフローの脆弱性

hack.cにおいて、コードが負のインデックス値をチェックしていなかったことを悪用し、攻撃者が特権を昇格させ、管理者になりました。
ここでの負のインデックスは、メモリへの不正な書き込みを引き起こし、フラグに影響を与えます。
フラグに影響を与え、非管理者ユーザーを管理者に変更しました。
バッファ・オーバーフロー脆弱性については、こちらをご覧ください:
https://owasp.org/www-community/vulnerabilities/Buffer_Overflow

// Vulnerability was in line 83 of code.h
// Fix can be found in line 77 below

#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_USERNAME_LEN 39
#define SETTINGS_COUNT 10
#define MAX_USERS 100
#define INVALID_USER_ID -1

// For simplicity, both the private (implementation specific) and the public (API) parts 
// of this application have been combined inside this header file. In the real-world, it
// is expected for the public (API) parts only to be presented here. Therefore, for the 
// purpose of this level, please assume that the private (implementation specific) sections 
// of this file, would not be known to the non-privileged users of this application

// Internal counter of user accounts
int userid_next = 0;

// The following structure is implementation-speicific and it's supposed to be unknown 
// to non-privileged users
typedef struct {
    bool isAdmin;
    long userid;
    char username[MAX_USERNAME_LEN + 1];
    long setting[SETTINGS_COUNT];
} user_account;

// Simulates an internal store of active user accounts
user_account *accounts[MAX_USERS];

// The signatures of the following four functions together with the previously introduced 
// constants (see #DEFINEs) constitute the API of this module

// Creates a new user account and returns it's unique identifier
int create_user_account(bool isAdmin, const char *username) {
    if (userid_next >= MAX_USERS) {
        fprintf(stderr, "the maximum number of users have been exceeded");
        return INVALID_USER_ID;
    }    

    user_account *ua;
    if (strlen(username) > MAX_USERNAME_LEN) {
        fprintf(stderr, "the username is too long");
        return INVALID_USER_ID;
    }    
    ua = malloc(sizeof (user_account));
    if (ua == NULL) {
        fprintf(stderr, "malloc failed to allocate memory");
        return INVALID_USER_ID;
    }
    ua->isAdmin = isAdmin;
    ua->userid = userid_next++;
    strcpy(ua->username, username);
    memset(&ua->setting, 0, sizeof ua->setting);
    accounts[userid_next] = ua;
    return userid_next++;
}

// Updates the matching setting for the specified user and returns the status of the operation
// A setting is some arbitrary string associated with an index as a key
bool update_setting(int user_id, const char *index, const char *value) {
    if (user_id < 0 || user_id >= MAX_USERS)
        return false;

    char *endptr;
    long i, v;
    i = strtol(index, &endptr, 10);
    if (*endptr)
        return false;

    v = strtol(value, &endptr, 10);
    // FIX: We should check for negative index values too! Scroll for the full solution
    if (*endptr || i < 0 || i >= SETTINGS_COUNT)
        return false;
    accounts[user_id]->setting[i] = v;
    return true;
}

// Returns whether the specified user is an admin
bool is_admin(int user_id) {
    if (user_id < 0 || user_id >= MAX_USERS) {
        fprintf(stderr, "invalid user id");
        return false;
    }    
    return accounts[user_id]->isAdmin;
}

// Returns the username of the specified user
const char* username(int user_id) {
    // Returns an error for invalid user ids
    if (user_id < 0 || user_id >= MAX_USERS) {
        fprintf(stderr, "invalid user id");
        return NULL;
    }    
    return accounts[user_id]->username;
}

まだまだいくぞ!第3問

Level 3

ソースコード

# Welcome to Secure Code Game Season-1/Level-3!

# You know how to play by now, good luck!

import os
from flask import Flask, request

### Unrelated to the exercise -- Starts here -- Please ignore
app = Flask(__name__)


@app.route("/")
def source():
    TaxPayer("foo", "bar").get_tax_form_attachment(request.args["input"])
    TaxPayer("foo", "bar").get_prof_picture(request.args["input"])


### Unrelated to the exercise -- Ends here -- Please ignore


class TaxPayer:

    def __init__(self, username, password):
        self.username = username
        self.password = password
        self.prof_picture = None
        self.tax_form_attachment = None

    # returns the path of an optional profile picture that users can set
    def get_prof_picture(self, path=None):
        # setting a profile picture is optional
        if not path:
            pass

        # defends against path traversal attacks
        if path.startswith("/") or path.startswith(".."):
            return None

        # builds path
        base_dir = os.path.dirname(os.path.abspath(__file__))
        prof_picture_path = os.path.normpath(os.path.join(base_dir, path))

        with open(prof_picture_path, "rb") as pic:
            picture = bytearray(pic.read())

        # assume that image is returned on screen after this
        return prof_picture_path

    # returns the path of an attached tax form that every user should submit
    def get_tax_form_attachment(self, path=None):
        tax_data = None

        if not path:
            raise Exception("Error: Tax form is required for all users")

        with open(path, "rb") as form:
            tax_data = bytearray(form.read())

        # assume that tax data is returned on screen after this
        return path

この問題では、1つの関数でパストラバーサル攻撃対策として(丁寧にコメントまでつけて)パスチェックをしている一方で、もう1つの関数では対策をしていないという、結構なサービス問題とみせかけて、実は罠になっているという良問 (?)です。

LLMの回答

モデル 指摘した脆弱性 提案した解決策
GPT4 パストラバーサル
任意のファイル読み取り
os.pathを使用して入力チェック
Gemini Advanced パストラバーサル ホワイトリスト方式
os.pathを使用して入力チェック
ファイルをデータベースに保存
Claude 3 Opus パストラバーサル
安全でないファイル操作
os.pathを使用して入力チェック
ファイルパスを開く前に、ファイルパスを検証し、サニタイズ
Llama2 70B パストラバーサル
安全でないファイルのアップロード
SQL インジェクション
情報漏洩
安全でない乱数生成
os.pathを使用して入力チェック
拡張子とコンテンツタイプを検証
プリペアドステートメント
ファイルパスのハッシュ化
secrets モジュール

Llama2くんは独特というか、かなり心配性ですね
脆弱性としては、ソースコードにも書かれているパストラバーサルが正解です。
解決策も似てますね

回答の全文

GPT4

提供されたコードを分析したところ、いくつかのセキュリティ上の脆弱性が確認された。それらの問題点、悪用の可能性、修正方法を順を追って説明しよう。

脆弱性 1: get_prof_picture におけるパストラバーサル

問題点

get_prof_picture` メソッドは、パスが '/' または '..' で始まるかどうかをチェックすることで、パストラバーサル攻撃を防御しようとしている。しかし、このチェックは不十分です。攻撃者は「./../」のようなバリエーションを使用してこの単純なチェックを回避し、意図したディレクトリ外のファイルにアクセスするパストラバーサル攻撃を実行することができます。

攻撃:

攻撃者はパスを操作することで、許可されたディレクトリ外のファイルを要求でき、サーバー上の機密ファイルにアクセスできる可能性がある。

修正

修正: 入力パスをサニタイズするために、より堅牢な方法を使用する。一つの方法は、パスを絶対形式に解決し、それがまだ意図したベースディレクトリ内に存在するかどうかをチェックすることです。

修正コードの例:

def get_prof_picture(self, path=None):
    if not path:
        pass

    base_dir = os.path.dirname(os.path.abspath(__file__))
    prof_picture_path = os.path.normpath(os.path.join(base_dir, path))

    # Ensure the resolved path starts with the base_dir path
    if not prof_picture_path.startswith(base_dir):
        return None

    with open(prof_picture_path, 'rb') as pic:
        picture = bytearray(pic.read())

    return prof_picture_path

脆弱性 2: get_tax_form_attachment における任意のファイル読み取り

問題

get_tax_form_attachmentメソッドはpath` パラメータで指定されたファイルを検証なしで直接開き、読み込む。これにより、攻撃者は機密ファイルを含む、サーバのファイルシステムからアクセス可能なあらゆるファイルを読み取ることができます。

攻略法

最初の脆弱性と同様に、攻撃者はパスを操作してサーバ上の任意のファイルを読み取ることができる。

修正

get_prof_picture`の修正と同様のパス検証を実装する。さらに、読み込めるファイルタイプをアプリケーションの機能に必要なものだけに制限することを検討する。

修正コードの例

def get_tax_form_attachment(self, path=None):
    if not path:
        raise Exception("Error: Tax form is required for all users")

    base_dir = os.path.dirname(os.path.abspath(__file__))
    tax_form_path = os.path.normpath(os.path.join(base_dir, path))

    if not tax_form_path.startswith(base_dir):
        raise Exception("Error: Access denied")

    with open(tax_form_path, 'rb') as form:
        tax_data = bytearray(form.read())

    return tax_form_path

セキュリティ全般に関する改善提案

  • インジェクション攻撃を防ぐために、特にファイル・パスやデータベース・クエリで使用される ユーザ入力を常に検証すること。
  • 安全な関数の使用:** ユーザー入力を安全に処理するように設計された、安全な関数やライブラリの使用を優先する。
  • エラー処理:*** エラーメッセージで多くの情報を明らかにすることは避けてください。情報漏洩を防ぐため、汎用的なエラーメッセージを提供する。
  • アクセス制御:*** アプリケーションが適切なアクセス制御を実装していることを確認し、機密機能への不正アクセ スを防止する。

これらの脆弱性に対処し、改善提案に従うことによって、チームはアプリケーションのセキュリティを大幅に強化することができます。

Gemini Advanced

もちろんです!コードを分解し、脆弱性を特定し、悪用のリスクについて話し合い、あなたのチームに安全なコーディングの実践方法を提供しましょう。

脆弱性分析

提供されたコードには、get_prof_pictureget_tax_form_attachment メソッドの両方に Directory Traversal (Path Traversal としても知られる) 脆弱性の潜在的なリスクがあります。

なぜこれが脆弱性なのか?

  • ユーザー制御の入力: 両方の関数はユーザー入力(request.args["input"])から直接ファイルパス(path)を取ります。ウェブアプリケーションでは、ユーザから入力されたものはすべて悪意のある可能性があります。
  • get_prof_picture`関数には基本的なトラバーサル攻撃を防ぐための試みがありますが、バイパスされる可能性があります。唯一のチェックは、指定されたパスがフォワードスラッシュ(/)で始まるか、ドットドットスラッシュ(../)で始まるかです。

これはどのように悪用されるのでしょうか?

攻撃者は path パラメータを操作して、意図したアプリケーションのディレクトリ外のファイルにアクセ スすることができます。以下にシナリオを示します:

1.機密ファイルへのアクセス: 攻撃者は、システムのパスワードファイルを読もうとするために、../../../../etc/passwdのような入力を提供する可能性がある。本番環境では、これには機密性の高いユーザー情報が含まれている可能性があります。

2.情報公開: アプリケーションの構造によっては、攻撃者はデータベースの認証情報や他の内部設定の詳細を明らかにする設定ファ イルを取得できるかもしれません。

おすすめ情報

  1. 堅牢な入力検証とサニタイゼーション:

    • ホワイトリスト方式:ファイル・パスに許される文字とパターンの厳格なリストを定義する。これらのルールに厳密に一致しないものはすべて拒否する。
    • os.path.basenameのような専用関数を使用して、ファイル名部分を抽出し、ディレク トリ・トラバーサルの試みを取り除きます。
  2. ファイルの保存と内部参照:

    • ユーザがアップロードしたコンテンツ(プロフィール写真、納税申告書)は、アプリケーションのコードベースの外側にある、その目的のために設計された特定のディレクトリに保存します。
    • ファイルパスを直接使用する代わりに、ファイルをデータベースに保存し、内部IDまたは参照を使用してそれらを取得することを検討してください。これにより、ユーザー入力とファイルシステムへの直接アクセスが切り離されます。

コード例(get_tax_form_attachmentの改訂版):.

import os

def get_tax_form_attachment(self, path=None):
    if not path:
        raise Exception("Error: Tax form is required for all users")

    allowed_extensions = ['.pdf', '.doc', '.docx']  # Whitelist file types
    filename = os.path.basename(path)
    if not any(filename.endswith(ext) for ext in allowed_extensions):
        raise Exception("Invalid file type")

    # Assuming you have a designated directory 
    tax_forms_dir = os.path.join(os.path.dirname(__file__), 'tax_forms')
    safe_path = os.path.join(tax_forms_dir, filename) 

    with open(safe_path, 'rb') as form:
        tax_data = bytearray(form.read())

    return tax_data  # File contents, not the path

チームのメンタリング

  • ディレクトリトラバーサル攻撃とその潜在的な影響について明確に説明する。実例を用いて、不適切な入力処理のリスクを強調する。

  • 安全なコーディングの実践: 防御の第一線として、入力検証とサニタイズの重要性を強調する。許容可能な文字のホワイトリストと内部参照の使用について議論する。

  • 共同レビュー:チームメンバーが互いの作業の潜在的な脆弱性を探すコードレビューの文化を奨励する。

  • get_prof_picture`メソッドの改訂版が必要な場合や、分析したい特定のシナリオがある場合は、私に知らせてください。セキュアなアプリケーションの構築は、継続的な学習プロセスです。

Claude 3 Opus

いい質問ですね!セキュリティの専門家として、提供されたコードの脆弱性を特定し、修正するプロセスを指導することができます。コードを分析し、ステップ・バイ・ステップで改善について話し合いましょう。

脆弱性1:パストラバーサル

get_prof_pictureメソッドとget_tax_form_attachmentメソッドの両方において、path` パラメータが適切な検証なしにファイルパスを構築するために使用されています。これにより、攻撃者はパストラバーサル攻撃を行い、意図したディレクトリ外のファイルにアクセスすることができます。

説明

  • get_prof_picture メソッドは path が '/' または '..' で始まっているかどうかをチェックすることで、パストラバーサル攻撃に対する防御を試みている。しかし、このチェックはパストラバーサルのバリエーションをすべてカバーしているわけではないので、不十分です。
  • get_tax_form_attachmentメソッドはパストラバーサルチェックを全く持っていないので、脆弱である。

悪用
攻撃者は、意図したディレクトリ外のファイルにアクセスするために、パストラバーサルシーケンスを含む悪意のある path 値を提供することができます。例えば

  • get_prof_picture("../../../etc/passwd")`は機密性の高いシステムファイルを取得することができる。
  • get_tax_form_attachment("../../../etc/passwd")`も機密ファイルにアクセスできます。

修正
パストラバーサル脆弱性を修正するには、os.path.abspath関数を使って、指定された path の絶対パスを取得し、それがベースディレクトリから始まっているかどうかをチェックする必要がある。以下に修正したコードの例を示す:

def get_prof_picture(self, path=None):
    if not path:
        pass

    base_dir = os.path.dirname(os.path.abspath(__file__))
    prof_picture_path = os.path.abspath(os.path.join(base_dir, path))

    if not prof_picture_path.startswith(base_dir):
        return None

    with open(prof_picture_path, 'rb') as pic:
        picture = bytearray(pic.read())

    return prof_picture_path

def get_tax_form_attachment(self, path=None):
    tax_data = None

    if not path:
        raise Exception("Error: Tax form is required for all users")

    base_dir = os.path.dirname(os.path.abspath(__file__))
    tax_form_path = os.path.abspath(os.path.join(base_dir, path))

    if not tax_form_path.startswith(base_dir):
        raise Exception("Error: Invalid tax form path")

    with open(tax_form_path, 'rb') as form:
        tax_data = bytearray(form.read())

    return tax_form_path

脆弱性 2:安全でないファイル操作

コードは、提供されたファイルパスの内容を、検証やサニタイズなしに直接読み取ります。これは、任意のファイルの読み込みや、悪意のあるコードの実行といった問題につながる可能性があります。

説明

  • get_prof_pictureメソッドとget_tax_form_attachmentメソッドは、ファイルの種類や内容をチェックせずにopen` 関数を使ってファイルを開きます。
  • 任意のファイルを読み込むと機密情報が漏洩する可能性があり、信頼できないコードを実行するとシステムを危険にさらす可能性がある。

悪用
攻撃者は、スクリプトや実行ファイルのような悪意のあるファイルを指すファイルパスを提供することができ、任意のコードの実行や機密データの開示につながる可能性があります。

対策
この脆弱性を軽減するためには、ファイルパスを開く前に、ファイルパスを検証し、サニタイズする必要があります。さらに、許可されるファイル・タイプを制限し、ファイル・コンテンツに対して必要なチェックを行います。以下は修正されたコードの例です:

import mimetypes

def get_prof_picture(self, path=None):
    if not path:
        pass

    base_dir = os.path.dirname(os.path.abspath(__file__))
    prof_picture_path = os.path.abspath(os.path.join(base_dir, path))

    if not prof_picture_path.startswith(base_dir):
        return None

    # Validate file type
    allowed_types = ['image/jpeg', 'image/png']
    file_type, _ = mimetypes.guess_type(prof_picture_path)
    if file_type not in allowed_types:
        return None

    with open(prof_picture_path, 'rb') as pic:
        picture = bytearray(pic.read())

    return prof_picture_path

def get_tax_form_attachment(self, path=None):
    tax_data = None

    if not path:
        raise Exception("Error: Tax form is required for all users")

    base_dir = os.path.dirname(os.path.abspath(__file__))
    tax_form_path = os.path.abspath(os.path.join(base_dir, path))

    if not tax_form_path.startswith(base_dir):
        raise Exception("Error: Invalid tax form path")

    # Validate file type
    allowed_types = ['application/pdf']
    file_type, _ = mimetypes.guess_type(tax_form_path)
    if file_type not in allowed_types:
        raise Exception("Error: Invalid tax form file type")

    with open(tax_form_path, 'rb') as form:
        tax_data = bytearray(form.read())

    return tax_form_path

固定コードでは

  • mimetypes`モジュールはファイルの拡張子からファイルタイプを推測するために使用される。
  • 許可されるファイルタイプは各メソッドの allowed_types リストで指定される。
  • ファイルタイプが許可されたタイプに含まれていない場合、例外が発生するか None が返される。

これらの改善により、任意のファイルの実行を防ぎ、許可されるファイルタイプを制限することで、悪意のあるコードを実行するリスクを軽減することができる。

結論

パストラバーサル脆弱性に対処し、安全なファイル操作を実装することで、アプリケーションのセキュリティを大幅に強化 することができます。常にユーザ入力を検証し、サニタイズすること、特定の基準に基づいてファイルアクセスを制限すること、そしてファイル 操作を安全に扱うことを忘れないでください。

新たに出現するセキュリティの脅威を先取りするために、定期的にコードを見直し、更新することが重要です。セキュアなコーディングの実践、コードレビューの実施、徹底的なテストの実施をチームに奨励し、脆弱性の特定と修正を積極的に行いましょう。

さらにご質問がある場合や、より詳しいガイダンスが必要な場合は、遠慮なくお尋ねください!

Llama2 70B

コードを見直した結果、対処すべきセキュリティの脆弱性をいくつか特定しました。それぞれの脆弱性と、それがどのように悪用されるかを説明し、問題を修正するためのコードの改善案を提示します。
1.安全でないファイルアクセス
get_prof_pictureメソッドはos.path.joinを使ってプロフィール画像のパスを作成する。しかし、このメソッドは入力を適切にサニタイズしていないため、パストラバーサル攻撃を受ける可能性があります。攻撃者は、意図したディレクトリの外をトラバースするような操作されたパスを提供することで、この脆弱性を悪用することができます。 提案パストラバーサル攻撃を防ぐために、固定ベースディレクトリでos.path.join` を使用し、入力パスをサニタイズすること。
Fixed Code:

def get_prof_picture(self, path=None):
if not path:
pass
base_dir = os.path.dirname(os.path.abspath(__file__))
prof_picture_path = os.path.normpath(os.path.join(base_dir, path))
with open(prof_picture_path, 'rb') as pic:
picture = bytearray(pic.read())
return prof_picture_path

2.安全でないファイルのアップロード
get_tax_form_attachmentメソッドは入力としてファイルパスを受け取り、検証やサニタイズなしにファイルの内容を読み込みます。この脆弱性は、マルウェアや実行可能コードを含む悪意のあるファイルをアップロードすることで悪用される可能性があります。 提案ファイル拡張子とコンテンツタイプを検証し、期待されるファイルタイプのみがアップロードされるようにする。ファイルの内容を読むには、rbモードでopen` するなど、安全な方法を使用してください。
コードを修正しました:

def get_tax_form_attachment(self, path=None):
if not path:
raise Exception("Error: Tax form is required for all users")
base_dir = os.path.dirname(os.path.abspath(__file__))
tax_form_path = os.path.normpath(os.path.join(base_dir, path))
if not os.path.isfile(tax_form_path):
return None
with open(tax_form_path, 'rb') as form:
tax_data = bytearray(form.read())
return tax_form_path

3.SQL インジェクション
TaxPayerクラスにはusernamepassword` 属性があり、認証に使用される可能性がある。しかし、ユーザ入力のサニタイズやバリデーションがないので、アプリケーションは SQL インジェクション攻撃に対して脆弱です。
提案SQLインジェクション攻撃を防ぐために、プリペアドステートメントやパラメータ化されたクエリを使用してください。
Fixed Code:

import sqlalchemy
class TaxPayer:
def __init__(self, username, password):
self.username = username
self.password = password
self.prof_picture = None
self.tax_form_attachment = None
def get_prof_picture(self, path=None):
# ...
def get_tax_form_attachment(self, path=None):
# ...
def authenticate(self):
# Use a secure authentication mechanism, such as SQLAlchemy's `select()` method
session = sqlalchemy.orm.session.Session()
user = session.query(TaxPayer).filter_by(username=self.username, password=self.password).first()
if user is None:
return None
return user

4.情報漏洩
get_prof_picture` メソッドはプロフィール画像のフルパスを返すので、 ユーザーのファイルシステムに関する機密情報が漏洩する可能性がある。
提案情報漏洩を防ぐために、ファイル名またはハッシュ化されたファイルパスのみを返すようにしましょう。
コードを修正しました:

def get_prof_picture(self, path=None):
if not path:
pass
base_dir = os.path.dirname(os.path.abspath(__file__))
prof_picture_path = os.path.normpath(os.path.join(base_dir, path))
with open(prof_picture_path, 'rb') as pic:
picture = bytearray(pic.read())
return picture

5.安全でない乱数生成
TaxPayerクラスはos.urandom を使用してランダムなパスワードを生成するが、これは暗号化目的には安全性に欠ける。 提案安全なパスワードを生成するには、secrets` モジュールのような安全な乱数生成器を使用すること。
コードを修正した:

import secrets
class TaxPayer:
def __init__(self, username, password):
self.username = username
self.password = secrets.token_hex(16)
self.prof_picture = None
self.tax_form_attachment = None
def get_prof_picture(self, path=None):
# ...
def get_tax_form_attachment(self, path=None):
# ...

これらは、提供されたコードのセキュリティを向上させるためのいくつかの提案にすぎません。セキュリティは継続的なプロセスであり、将来的に対処すべき脆弱性が他にもあるかもしれないことを心に留めておくことが重要です。

それでは正解の発表です!

正解

パストラバーサル脆弱性
パスがサニタイズされていないため、ユーザは不正なパス(./付き)を渡すことができます。ユーザー入力を信頼する代わりに、os.pathlibraryに依存してベースディレクトリを導出するのが簡単な解決策です。ユーザー入力は、後で安全に生成されたベースディレクトリに追加することができるので、絶対ファイルパスが正規化されます。ベース・ディレクトリと正規化されたファイルパスの間の最長共通サブパスのチェックを追加し、逆引きが起こらないようにし、最終的なパスが意図したディレクトリになるようにする。

各LLMの提案した解決策を実行した結果、以下のようになりました

  • GPT4:全てのテストにパス👏(例外処理のせいでエラーになるけどOK)
  • Gemini Advanced: ホワイトリスト方式でパスチェックをしたためテストは失敗
  • Claude 3 Opus: 全てのテストにパス👏(例外処理のせいでエラーになるけどOK)
  • Llame2: パスを絶対パスに直しているのに入力パスの検証をしていないためhackに失敗

暫定での得点は以下のようになります

Level1 Level2 Level3 Level5 総合得点
GPT4 脆弱性: 2
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 3
脆弱性:
解決策:
7
Gemini Advanced 脆弱性: 1
解決策: 0
脆弱性: 2
解決策: 3
脆弱性: 1
解決策: 0
脆弱性:
解決策:
7
Claude 3 Opus 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 3
脆弱性:
解決策:
6
Llama2 70B 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性:
解決策:
3

ここでGPT4が並んだァー!!オレこそがLLMのパイオニアだと言わんばかりの猛追ッッ!
気づけばClaude3もすぐ後ろだーーッ!

解説

パストラバーサル脆弱性
get_prof_picture関数とget_tax_form_attachment関数では、パスがサニタイズされていないため、ユーザは不正なパス(./付き)を渡すことができます。
入力のバリデーションは、許容される文字セットを英数字に制限することで、最初のうちは良い解決策のように思えますが、このアプローチが制限的すぎることがあります。任意のファイル名を扱う必要があるかもしれないし、コードがクロスプラットフォームで動作し、Windows、Mac、*nix間のファイルシステムの違いを考慮する必要があるかもしれない。
提案する修正方法:無効なパス(ドットドットなど)をチェックすることで、文字列ベースのテストを改善することは可能だが、入力のスペクトルが無限になる可能性があり、攻撃者が実にクリエイティブになるため、このアプローチは危険である。
代わりに、ユーザー入力を信頼する代わりに、os.pathlibraryに依存してベースディレクトリを導出するのが簡単な解決策です。ユーザー入力は、後で安全に生成されたベースディレクトリに追加することができるので、絶対ファイルパスが正規化されます。
最後に、ベース・ディレクトリと正規化されたファイルパスの間の最長共通サブパスのチェックを追加し、逆引きが起こらないようにし、最終的なパスが意図したディレクトリになるようにする。
GitHubセキュリティ・ラボは、安全なプログラミングに関するシリーズであるSecurityBitesの1つのエピソードでこの欠陥を取り上げました: https://youtu.be/sQGxdwRePh8
また、OWASPのプロアクティブ・コントロールのトップ10に関するブログ記事でもこの欠陥を取り上げた:https://github.blog/2021-12-06-write-more-secure-code-owasp-top-10-proactive-controls/。

import os

def safe_path(path):
    base_dir = os.path.dirname(os.path.abspath(__file__))
    filepath = os.path.normpath(os.path.join(base_dir, path))
    if base_dir != os.path.commonpath([base_dir, filepath]):
        return None
    return filepath

第4問にいきたいところですが、Level 4はソースコードが長い上に答えもシンプルなので、省略します(たぶん全LLMが正解できる)

ということでラスト!

Level 5

ソースコード

# Welcome to Secure Code Game Season-1/Level-5!

# This is the last level of our first season, good luck!

import binascii
import random
import secrets
import hashlib
import os
import bcrypt

class Random_generator:

    # generates a random token
    def generate_token(self, length=8, alphabet=(
    '0123456789'
    'abcdefghijklmnopqrstuvwxyz'
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    )):
        return ''.join(random.choice(alphabet) for _ in range(length))

    # generates salt
    def generate_salt(self, rounds=12):
        salt = ''.join(str(random.randint(0, 9)) for _ in range(21)) + '.'
        return f'$2b${rounds}${salt}'.encode()

class SHA256_hasher:

    # produces the password hash by combining password + salt because hashing
    def password_hash(self, password, salt):
        password = binascii.hexlify(hashlib.sha256(password.encode()).digest())
        password_hash = bcrypt.hashpw(password, salt)
        return password_hash.decode('ascii')

    # verifies that the hashed password reverses to the plain text version on verification
    def password_verification(self, password, password_hash):
        password = binascii.hexlify(hashlib.sha256(password.encode()).digest())
        password_hash = password_hash.encode('ascii')
        return bcrypt.checkpw(password, password_hash)

class MD5_hasher:

    # same as above but using a different algorithm to hash which is MD5
    def password_hash(self, password):
        return hashlib.md5(password.encode()).hexdigest()

    def password_verification(self, password, password_hash):
        password = self.password_hash(password)
        return secrets.compare_digest(password.encode(), password_hash.encode())

# a collection of sensitive secrets necessary for the software to operate
PRIVATE_KEY = os.environ.get('PRIVATE_KEY')
PUBLIC_KEY = os.environ.get('PUBLIC_KEY')
SECRET_KEY = 'TjWnZr4u7x!A%D*G-KaPdSgVkXp2s5v8'
PASSWORD_HASHER = 'MD5_hasher'

LLMの回答

モデル 指摘した脆弱性 提案した解決策
GPT4 randomモジュールの脆弱性
MD5 ハッシュアルゴリズムの脆弱性
ハードコードされた秘密鍵
安全でないsalt生成
secrets モジュールを使用
SHA-256やbcryptの使用
環境変数または安全な保管庫に保存
bcrypt.gensalt() を使用
Gemini Advanced MD5の脆弱性
機密データの安全でない保管
random モジュールの脆弱性
bcrypt、scrypt、Argon2などを使用
AWS Secrets Managerなどを使用
secrets モジュールの使用
Claude 3 Opus random モジュールの脆弱性
SHA-256の脆弱性
MD5の脆弱性
ハードコードされた秘密鍵
パスワードハッシュの安全でない比較
secretsモジュールの使用
bcrypt、scrypt、PBKDF2などの使用
環境変数など
secrets.compare_digest を使って直接比較
Llama2 70B randomモジュールの脆弱性
弱いパスワードが平文で保存されてしまう可能性
安全でないソルト生成
秘密鍵の安全でない保管
パスワードハッシュの安全でない比較
os.urandomsecrets.rand belowを使用
passlibpython-passwords を使用
secrets.randbelowなどを使用
環境変数やセキュアな設定ファイル
passlibの使用

回答の全文

GPT4

提供されたコードを分析すると、いくつかのセキュリティ上の脆弱性を特定することができる。それらを順を追って調べ、なぜ問題なのか、どのように悪用されるのかを理解しましょう。そして、コード改善の提案と、修正されたコードの例を出力します。

脆弱性 1: random をセキュリティ上重要な操作に使用する

問題点

Random_generator クラスはトークンとsaltの生成に random モジュールを使用しているが、これは暗号的に安全ではない。これは random の出力を予測することによって悪用される可能性があり、トークンの予測やソルトの推測のような潜在的な攻撃につながる。

修正

トークンとソルトの生成には secrets モジュールを使用する。

修正例

import secrets

class Random_generator:

    # generates a random token using the secrets module
    def generate_token(self, length=8, alphabet=("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")):
        return ''.join(secrets.choice(alphabet) for _ in range(length))

    # generates salt using the secrets module
    def generate_salt(self, rounds=12):
        salt = secrets.token_urlsafe(16)
        return bcrypt.gensalt(rounds=rounds, prefix=b"2b")

脆弱性 2: パスワードハッシュに MD5 を使用する

問題

MD5_hasher クラスは MD5 ハッシュアルゴリズムを使用している。これは衝突攻撃に対して脆弱であり、異なる入力で同じハッシュを生成することが可能である。

修正

パスワードのハッシュに MD5 を使用しない。代わりに、SHA-256 のようなより安全なハッシュ・アルゴリズムを使用するか、レインボー・テーブル攻撃から保護するためのソルトが含まれている bcrypt をパスワード・ハッシュに直接使用するのがさらに良いでしょう。

修正例

# Remove MD5_hasher class entirely and rely on bcrypt for password hashing.

脆弱性3:ハードコードされた秘密鍵

問題点

SECRET_KEY はソースコードにハードコードされているため、そのコードにアクセスできる人なら誰でも見ることができます。このキーはアプリケーションを攻撃したり、機密情報を解読したりするために使用される可能性があります。

修正

修正: 機密鍵や秘密鍵は、実行時にアプリケーションがアクセスできる環境変数または安全な保管庫に保存してください。

修正例

import os

# Use environment variables for sensitive secrets
SECRET_KEY = os.environ.get("SECRET_KEY")

脆弱性 4: generate_salt における安全でないsalt生成

問題

generate_salt メソッドは非標準的な方法で salt を生成し、安全でない random モジュールに依存している。これにより、クラックされやすい弱いsaltが生成される可能性がある。

修正:

saltの生成には bcrypt.gensalt() を使用する。この目的のために設計されており、saltが暗号的に安全であることを保証するからである。

修正例

# Updated in the Random_generator class fix example above.

全般的な改善:検証のための secrets.compare_digest の使用

MD5_hasherpassword_verification メソッドでは、パスワードハッシュの比較に secrets.compare_digest を使用しています。すべてのパスワード検証の実装で、この方法を使用するようにしてください。

結論

これらの脆弱性に対処することで、アプリケーションは一般的な攻撃に対してより安全になります。アプリケーションでパスワード、トークン、およびその他の機密情報を扱うために、常に最新で安全なプラク ティスを使用することが極めて重要です。

Gemini Advanced

もちろんです!あなたのセキュリティ専門家として、提供されたシナリオの包括的なコード分析に飛び込んでみましょう。

主な脆弱性と推奨事項をご覧ください。

核心的な問題点と改善点は、これらの分野に集中しています:

1.MD5による不十分なハッシュ
2.機密データの安全でないストレージ
3.トークン生成における潜在的な弱点

それぞれを分解してみよう:

1.MD5による不十分なハッシング

  • 問題点: MD5_hasherクラスはパスワードのハッシュをMD5に依存している。 MD5は衝突(異なる入力が同じハッシュを生成する)に対して脆弱であることが何年も前から知られており、レインボーテーブル(事前に計算されたハッシュのテーブル)を使って簡単にクラックすることができます。
  • 悪用:MD5でハッシュ化されたパスワードのデータベースにアクセスした攻撃者は、比較的容易 にパスワードを逆変換することができ、ユーザー認証情報を危険にさらすことができる。
  • 推奨: bcrypt、scrypt、Argon2 のような強力で最新のパスワードハッシュアルゴリズムに切り替えてください。あなたのコードはすでにbcryptを使用していますが、これは優れた選択です。MD5_hasherクラスを削除し、PASSWORD_HASHER変数を"bcrypt_hasher"`に変更する(これを作成する)。コードを修正しよう:
...
import bcrypt  # Already imported

class Bcrypt_hasher:  # Use bcrypt throughout
    def password_hash(self, password, salt):
        password_hash = bcrypt.hashpw(password.encode(), salt)
        return password_hash.decode("ascii")

    def password_verification(self, password, password_hash):
        password_hash = password_hash.encode("ascii")
        return bcrypt.checkpw(password.encode(), password_hash)

...

PASSWORD_HASHER = "Bcrypt_hasher"  # Use bcrypt across the board

**2.機密データの安全でない保管

  • 問題点
    • PRIVATE_KEYPUBLIC_KEYSECRET_KEYのような機密性の高い値は、コードに直接埋め込むか、環境変数から取得します。
    • コードベースにアクセスできる人なら誰でもキーを取得できるため、コードに格納するのは非常に安全ではない。
    • 環境変数は少しましですが、それでもプロセスリストやシステムログを通して公開される可能性があり、理想的ではありません。
  • 悪用: これらの鍵を抜き取る攻撃者は、これらの鍵がどのように使われるかに応じて、あなたのシス テムの様々な側面を危険にさらす可能性がある。
  • 推奨:専用の秘密管理ソリューションを使用してください:
    • HashiCorp Vault、AWS Secrets Managerなどのシステムは、秘密の安全な保管、ローテーション、アクセス制御を提供します。
    • 実行時に選択したソリューションからシークレットをフェッチするようにコードをリファクタリングしてください。

3.トークン生成における潜在的な弱点

  • トークン生成における潜在的な弱点 問題点: generate_token 関数は random モジュールを使用しています。
  • 悪用:** トークンが重要な機能に使用される場合、予測可能性が悪用につながる可能性があります。
  • 推奨:** 暗号的に安全なランダム生成のために secrets モジュールに切り替えること:
class Random_generator:
    def generate_token(self, length=8):  # Simplified for demonstration 
        return secrets.token_urlsafe(length)  
    ... 

チームのメンタリング

開発者を指導するときは、次のことを強調する:

  • 設計によるセキュリティ:*** 開発の初期段階からセキュリティについて考えることの重要性を植え付ける。
  • 複数のセキュリティ層(強力なハッシュ、秘密管理、安全なランダム生成)を実装することで、侵害をはるかに困難にする。
  • セキュリティは動的なものであり、ベストプラクティスと進化する脆弱性についての継続的な学習を奨励する。

さらに詳細な分析が必要な場合や、特定のコード部分について質問がある場合は、私に知らせてください!

Claude 3 Opus

確かに!世界的なセキュリティの専門家として、私は、与えられたコードのセキュリティの脆弱性を特定し、改善のための提案をするプロセスを通して、あなたを指導します。ステップ・バイ・ステップでコードを分析してみましょう。

脆弱性1:弱いランダムトークン生成
Random_generator クラスの generate_token メソッドは random.choice 関数を使用してランダムなトークンを生成する。しかし、random モジュールは暗号学的に安全ではないので、機密性の高いトークンの生成に使用すべきではない。

悪用される可能性攻撃者は生成されたトークンを予測または推測し、不正アクセスやトークンの偽造につながる可能性がある。

提案Pythonのsecretsモジュールのような暗号的に安全な乱数生成器を使用して、ランダムなトークンを生成してください。

コードを修正しました:

def generate_token(self, length=8, alphabet="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"):
    return "".join(secrets.choice(alphabet) for _ in range(length))

脆弱性2:安全でないパスワードハッシュ
SHA256_hasherクラスはSHA-256を使ってパスワードをハッシュするが、安全なパスワードハッシュアルゴリズムを使っていない。SHA-256は高速なハッシュのために設計されており、パスワードの保存には適していません。

悪用攻撃者がハッシュ化されたパスワードにアクセスした場合、事前に計算されたハッシュテーブル(レインボーテーブル)やブルートフォース技術を使って、より簡単にパスワードをクラックすることができます。

提案bcrypt、scrypt、PBKDF2のような、専用のパスワード・ハッシュ・アルゴリズムを使用すること。

コードを修正しました:

class PasswordHasher:
    def password_hash(self, password):
        salt = bcrypt.gensalt()
        password_hash = bcrypt.hashpw(password.encode(), salt)
        return password_hash.decode("ascii")

    def password_verification(self, password, password_hash):
        password_hash = password_hash.encode("ascii")
        return bcrypt.checkpw(password.encode(), password_hash)

脆弱性3:弱いハッシュアルゴリズム(MD5)の使用
MD5_hasherクラスはMD5ハッシュアルゴリズムを使用するが、これは衝突の脆弱性があることで知られており、パスワードのハッシュや機密性の高いデータには安全でないと考えられている。

悪用:攻撃者は MD5 の弱点を突いてハッシュの衝突を発生させ、パスワードのクラッキングやデータの完全性の問題を引き起こす可能性があります。

提案パスワード・ハッシュやセキュリティ上重要な操作にMD5を使用することは避けてください。代わりに、前の提案で述べたように、安全なパスワード・ハッシュ・アルゴリズムを使用してください。

脆弱性4:ハードコードされた秘密鍵
このコードにはハードコードされた秘密鍵 (SECRET_KEY) がソースコードに直接含まれています。秘密鍵のような機密情報をプレーンテキストで保存することはセキュリティリスクです。

悪用:攻撃者がソースコードにアクセスすると、秘密鍵を簡単に入手することができ、偽造トークンに署名したり、機密データにアクセスしたりといった不正な目的に使用することができます。

提案秘密鍵のようなセンシティブな情報は、環境変数やセキュアなコンフィギュレーション管理システムを使うなど、安全な方法で保存すること。ソース・コードにハード・コードすることは避けてください。

コードを修正しました:

SECRET_KEY = os.environ.get("SECRET_KEY")

脆弱性5:パスワードハッシュの安全でない比較
MD5_hasherクラスでは、password_verification メソッドは secrets.compare_digest を使ってパスワードハッシュを比較する。しかし、比較はパスワードハッシュをエンコードした後に行われるため、タイミング攻撃につながる可能性がある。

悪用:攻撃者は、比較操作のタイミングの違いを悪用して、正しいパスワードハッシュをバイト単位で決定できる可能性があります。

提案パスワードハッシュをエンコードせずに secrets.compare_digest を使って直接比較する。

修正されたコード

def password_verification(self, password, password_hash):
    password = self.password_hash(password)
    return secrets.compare_digest(password, password_hash)

これらは、提供されたコードで確認された主要なセキュリティ脆弱性の一部です。より堅牢で安全なアプリケーションを構築するためには、これらの問題に対処し、セキュアなコーディングプラクティスに従うことが極めて重要です。

依存関係を常に最新の状態に保ち、定期的にコードの脆弱性をレビューし、監査し、開発プロセス全体を通じて、確立されたセ キュリティガイドラインとベストプラクティスに従うことを忘れないでください。

さらに質問があったり、より多くの指導が必要であれば、遠慮なく尋ねてください!

Llama2 70B

ワールドクラスのセキュリティ専門家として、私は与えられたコードを分析し、対処すべきいくつかの脆弱性を発見しました。それぞれの脆弱性と、それがどのように悪用されるかを説明し、問題を修正するためのコードの改善案を提供します。
1.安全でない乱数生成
Random_generatorクラスはrandom モジュールを使ってランダムなトークンと salt を生成する。しかし、random モジュールは暗号的に安全な乱数の生成には適していない。攻撃者は生成されたトークンとソルトを予測することができ、攻撃に対して脆弱である。 提案os.urandomsecrets.rand belowのような暗号的に安全な擬似乱数生成器(CSPRNG)を使用して、ランダムなトークンとソルトを生成する。
2.弱いパスワードハッシュ
SHA256_hasher クラスは bcrypt ライブラリを使用してパスワードをハッシュする。しかし、password_hash メソッドは生のパスワードを入力として受け取るため、弱いパスワードが平文で保存されてしまう可能性がある。さらに、password_verification メソッドは encode メソッドを使ってパスワードハッシュを ASCII に変換するが、これはエンコーディングの問題につながる可能性がある。
提案passlibpython-passwords のようなセキュアなパスワードハッシュライブラリを使用して、パスワードのハッシュ化と検証を行う。これらのライブラリは安全なパスワードハッシュと検証の仕組みを提供し、エンコードの問題も適切に処理します。
3.安全でないソルト生成
Random_generator クラスは、ランダムな数字と文字を連結した単純なアルゴリズムを使ってソルトを生成する。このソルト生成方法は攻撃者に簡単に予測されてしまうため、攻撃に対して脆弱である。
提案ソルトを生成するには、secrets.randbelowメソッドのような安全なソルト生成メソッドを使用してください。この方法は予測しにくい安全なソルトを生成する。
4.機密性の高い秘密の安全でない保管
変数 PRIVATE_KEYPUBLIC_KEYSECRET_KEY は平文としてコードに格納される。これは、攻撃者がこれらの機密情報に簡単にアクセスして盗むことができるため、安全とは言えない。
提案これらのセンシティブな秘密は、環境変数やセキュアな設定ファイルを使って安全に保存してください。os.environconfigparserのようなライブラリを使うと、安全に扱うことができます。 5.パスワードハッシュの安全でない比較 password_verificationメソッドはsecrets.compare_digest メソッドを使用して、ハッシュ化されたパスワードと保存されているパスワードハッシュを比較する。しかし、この方法はタイミング攻撃につながる可能性があり、安全ではありません。 提案passlibライブラリが提供するConstantTimeCompare` メソッドのような安全な比較メソッドを使用してください。このメソッドは定時間比較を提供し、タイミング攻撃から保護します。
これらの脆弱性に対処するために、コードをどのように改善できるかの例を示します:

import os
import secrets
import hashlib
import bcrypt
from passlib.hash import BcryptHash
class Random_generator:
def generate_token(self, length=8, alphabet=(
"0123456789" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
):
return "".join(secrets.randbelow(alphabet) for _ in range(length))
def generate_salt(self, rounds=12):

return secrets.randbelow(21) + "." + secrets.randbelow(21)
class SHA256_hasher:
def password_hash(self, password):
return hashlib.sha256(password.encode()).hexdigest()
def password_verification(self, password, password_hash):
password = hashlib.sha256(password.encode()).hexdigest()
return secrets.compare_digest(password.encode(), password_hash.encode())
class MD5_hasher:
def password_hash(self, password):
return hashlib.md5(password.encode()).hexdigest()
def password_verification(self, password, password_hash):
password = hashlib.md5(password.encode()).hexdigest()
return secrets.compare_digest(password.encode(), password_hash.encode())
# a collection of sensitive secrets necessary for the software to operate
PRIVATE_KEY = os.environ.get("PRIVATE_KEY")
PUBLIC_KEY = os.environ.get("PUBLIC_KEY")
SECRET_KEY = secrets.randbelow(21) + "." + secrets.randbelow(21)
PASSWORD_HASHER = "BCryptHash"
# Output:
# Generated token: 87654321
# Generated salt: 123456789.123456789
# Password hash: $2b$12$TjWnZr4u7x!A%D*G-KaPdSgVkXp2s5v8
# Password verification: True

この例では、secretsモジュールを使って安全な乱数と塩を生成している。また、 passlib ライブラリを使ってパスワードのハッシュ化と検証を行った。さらに、機密性の高い秘密は環境変数を使って安全に保存している。
これはコードを改善する一つの方法に過ぎないことに注意してほしい。これらの脆弱性に対処する方法は他にもあり、具体的な改善点はアプリケーションの要件と制約に依存する。

それでは正解発表です

正解

...の前に物言いが入ります

この理由についてGPT4に聞いてみると、
SHA-256が元々高速で計算できるように設計されているため、ブルートフォース攻撃(全ての可能性を試す攻撃)やレインボーテーブル攻撃(事前に計算されたハッシュ値の大規模なデータベースを使用する攻撃)に対して脆弱である
とのこと。この点はGithubが用意した正解には書かれていませんが、Code scanningで解決策を評価するため、脆弱性に追加します。

というわけで、正解は以下のようになります。

  1. 暗号化に適さないモジュールの使用
    randomおよびMD5は暗号化には適さない。代わりにsecretsなどを使用する
  2. gensalt関数の自作
    わざわざ自作する必要がない(車輪の再発明というらしいです)
  3. 秘密鍵をハードコードしている
    環境変数など安全な場所に保管すべき
  4. sha256の脆弱性
    上で説明した通り

Claude3はsha256の脆弱性に触れており、かつbcrypt.hashpwという正しい代替案を出しているので、テストにパスしたとみなし、それ以外は触れていないので失敗とみなします。

得点の最終結果は以下のようになりました

Level1 Level2 Level3 Level5 総合得点
GPT4 脆弱性: 2
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 3
脆弱性: 4
解決策: 0
11
Gemini Advanced 脆弱性: 1
解決策: 0
脆弱性: 2
解決策: 3
脆弱性: 1
解決策: 0
脆弱性: 3
解決策: 0
10
Claude 3 Opus 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 3
脆弱性: 4
解決策: 3
13
Llama2 70B 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 3
解決策: 0
6

なんと、超新星Cluad3が差し切ったーーーー!!

解説

解決策の説明
暗号的に壊れたアルゴリズムを選んだり、秘密鍵をソースコードに直接書き込んだりするような、基本的なミスもある。ランダムに見えるが、弱いランダム性を生成する関数を使用するような、より高度なものに陥る可能性が高い。gensalt()を呼び出す代わりに手動でソルトを生成することで、車輪の再発明をしている。random モジュールを使っていることに注意してください。このモジュールはモデリングとシミュレーションのために設計されており、セキュリティや暗号化のために設計されているわけではありません。暗号関連のユースケースには、特別に設計され、セキュリティ・コミュニティによって安全性が確認されたモジュールを使うのが良い習慣です。
コードを修正するために、私たちはオペレーティング・システム上で最も安全なランダム性のソースにアクセスできる「secrets」モジュールを使った。また、安全なトークンと推測しにくいURLを生成する関数も使いました。セキュリティ・コミュニティによって承認され、推奨されている他のpythonモジュールには、argon2とpbkdf2がある。

import binascii
import secrets
import hashlib
import os
import bcrypt

class Random_generator:

    # generates a random token using the secrets library for true randomness
    def generate_token(self, length=8, alphabet=(
    '0123456789'
    'abcdefghijklmnopqrstuvwxyz'
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    )):
        return ''.join(secrets.choice(alphabet) for i in range(length))

    # generates salt using the bcrypt library which is a safe implementation
    def generate_salt(self, rounds=12):
        return bcrypt.gensalt(rounds)

class SHA256_hasher:

    # produces the password hash by combining password + salt because hashing
    def password_hash(self, password, salt):
        password = binascii.hexlify(hashlib.sha256(password.encode()).digest())
        password_hash = bcrypt.hashpw(password, salt)
        return password_hash.decode('ascii')

    # verifies that the hashed password reverses to the plain text version on verification
    def password_verification(self, password, password_hash):
        password = binascii.hexlify(hashlib.sha256(password.encode()).digest())
        password_hash = password_hash.encode('ascii')
        return bcrypt.checkpw(password, password_hash)

# a collection of sensitive secrets necessary for the software to operate
PRIVATE_KEY = os.environ.get('PRIVATE_KEY')
PUBLIC_KEY = os.environ.get('PUBLIC_KEY')
SECRET_KEY = os.environ.get('SECRET_KEY')
PASSWORD_HASHER = 'SHA256_hasher'

結果発表

最終結果はこのようになりました

🥇第一位: Claude3 Opus

🥈第二位: GPT4

🥉第三位: Gemini Advanced

🎖️頑張ったで賞: Llama2 70B

Level1 Level2 Level3 Level5 総合得点
GPT4 脆弱性: 2
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 3
脆弱性: 4
解決策: 0
11
Gemini Advanced 脆弱性: 1
解決策: 0
脆弱性: 2
解決策: 3
脆弱性: 1
解決策: 0
脆弱性: 3
解決策: 0
10
Claude 3 Opus 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 3
脆弱性: 4
解決策: 3
13
Llama2 70B 脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 1
解決策: 0
脆弱性: 3
解決策: 0
6

感想

結構いい勝負してくれて感動すら覚えました。
Claude3にダブルスコアつけられちゃったけど、Llamaくんは独特な観点からみんなとは違う意見を出してくれたりしてある意味MVPだったり、Gemini Advancedくんはコードの修正以外にも気をつけるべきことを教えてくれたり、みんなちがってみんないいなぁ、と思いました。
ちなみに自分は一問も解けませんでした😇
LLMに勝った方はコメントで教えてください〜〜

最後に

この記事書くためにClaudeをサブスクしてしまったため、Season2も見たい方はバッチで応援してくださると、ひじょ〜〜〜に助かります。

ここまでお付き合いしてくださったみなさま、またSeason2でお会いしましょう!

Discussion