0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ】Bit Flipping Attack とは何か ― userが root に変わる理由と実験コード

0
Last updated at Posted at 2026-01-02

はじめに

Bit Flipping Attack(ビット反転攻撃)は、

暗号アルゴリズムを破らずに、
暗号文を改ざんして復号後の平文を変化させる攻撃

です。

この攻撃は AES が弱いから起きるのではありません
暗号化はしているが、改ざん検知をしていない
――つまり Unauthenticated Encryption を使っている場合に成立します。

本記事では、
"user""root" という具体例を使って、
Bit Flipping Attack が なぜ・どうやって成立するのかを解説します。


1. Bit Flipping Attack とは

Bit Flipping Attack とは、

  • 暗号文の 一部のビットを反転(XOR)
  • その結果、復号後の平文が 意図した値に変わる

という攻撃です。

特徴は次の通りです:

  • 秘密鍵は不要
  • 暗号の復号は正常に成功する
  • サーバは改ざんに気づかない

つまり、

「復号できた=正しい」と信じる実装が攻撃される

という攻撃です。


2. AES-CBC が Bit Flipping に弱い理由

AES-CBC モードの復号処理は次の式で表されます:

P_i = D_K(C_i) XOR C_{i-1}
  • P_i:復号後の平文ブロック
  • C_i:現在の暗号ブロック
  • C_{i-1}:前の暗号ブロック(最初は IV)

重要なポイント

  • 攻撃者は C_{i-1}(または IV)を書き換えられる
  • XOR の性質により、平文がその差分だけ変化する

これが Bit Flipping(ビット反転) です。


3. "user""root" はなぜ可能なのか

3.1 文字数が同じ

  • "user" → 4文字
  • "root" → 4文字

Bit Flipping では 文字数は変えられません
同じ長さだからこそ成立します。


3.2 ASCII と XOR 差分の計算

"user" の ASCII
文字 hex
u 0x75
s 0x73
e 0x65
r 0x72
"root" の ASCII
文字 hex
r 0x72
o 0x6f
o 0x6f
t 0x74
XOR 差分
位置 user root XOR
0 0x75 0x72 0x07
1 0x73 0x6f 0x1c
2 0x65 0x6f 0x0a
3 0x72 0x74 0x06

この差分を 前の暗号ブロック(または IV)に XOR すると、

"user" XOR diff = "root"

になります。


4. 実験コード

4.1 脆弱な AES-CBC トークンを生成

依存関係
pip install pycryptodome
トークン生成コード
def encrypt_cbc(plaintext:bytes,key:bytes) -> bytes:
    iv = get_random_bytes(BLOCK)
    cipher = AES.new(key,AES.MODE_CBC,iv)
    ciphertext = cipher.encrypt(pad(plaintext,BLOCK))
    return iv+ciphertext

このトークンは:

  • AES-CBC
  • IV を含む
  • MAC / 認証タグなし

Bit Flipping に完全に脆弱です。


4.2 "user" → "root" に Bit Flip する

def bitflip_prev_block(token:bytes,target_plain_block_index:int,offset_in_block:int,diff:bytes)->bytes:
    """
    CBCの基本:Pi を変えたいなら C_{i-1} を XOR する(i=1ならIV)
    target_plain_block_index: 0-based の平文ブロック番号 (P1=0, P2=1, ...)
    offset_in_block: 対象平文ブロック内で置換を始めるオフセット (0..15)
    diff: old XOR new のバイト列
    """
    raw= bytearray(token)

    # 直前ブロック開始位置(IVをブロック0とみなす)
    prev_block_index = target_plain_block_index
    prev_start = prev_block_index*BLOCK
    prev_end= prev_start+BLOCK

    if prev_end > len(raw):
        raise ValueError("ブロック指定がトークン長を超えています")
    if offset_in_block+len(diff) >BLOCK:
        raise ValueError("offset+len(diff)が16バイト境界を超えています")
    for i,d in enumerate(diff):
        raw[prev_start+offset_in_block+i]^=d
    
    return bytes(raw)

この modified token を復号すると:

{"role":"root"}

になります。


4.3 トークンから平文に復号する

def decrypt_cbc(token:bytes,key:bytes)->bytes:
    if len(token) < BLOCK * 2:
        raise ValueError(f"token too short: {len(token)} bytes")
    if len(token) % BLOCK != 0:
        raise ValueError(f"token length not multiple of block: {len(token)}")

    iv, ct = token[:BLOCK], token[BLOCK:]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    pt_padded = cipher.decrypt(ct)
    return unpad(pt_padded, BLOCK)

5.なぜサーバは気づかないのか

  • 復号は正常に成功する
  • MAC / 署名を検証していない
  • 復号結果をそのまま信用している

改ざんは検出されない


6. Bit Flipping Attack の成立条件まとめ

条件 内容
暗号 AES-CBC など
認証 なし(MACなし)
実装 復号結果を信頼
攻撃 暗号文を改ざん可能

7.対策(重要)

7.1推奨

  • AES-GCM
  • ChaCha20-Poly1305

7.2 CBC を使うなら

  • Encrypt-then-MAC
  • 復号前に MAC 検証

まとめ

  • Bit Flipping Attack は 暗号の設計ミスを突く攻撃
  • "user""root" は XOR 差分で実現できる
  • 鍵は不要、暗号は壊れていない
  • 暗号化だけでは安全ではない

Source Code

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad,unpad
from binascii import hexlify,unhexlify

BLOCK =16

def encrypt_cbc(plaintext:bytes,key:bytes) -> bytes:
    iv = get_random_bytes(BLOCK)
    cipher = AES.new(key,AES.MODE_CBC,iv)
    ciphertext = cipher.encrypt(pad(plaintext,BLOCK))
    return iv+ciphertext

def decrypt_cbc(token:bytes,key:bytes)->bytes:
    if len(token) < BLOCK * 2:
        raise ValueError(f"token too short: {len(token)} bytes")
    if len(token) % BLOCK != 0:
        raise ValueError(f"token length not multiple of block: {len(token)}")

    iv, ct = token[:BLOCK], token[BLOCK:]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    pt_padded = cipher.decrypt(ct)
    return unpad(pt_padded, BLOCK)

def xor_diff(old:bytes,new:bytes)->bytes:
    if len(old)!=len(new):
        raise ValueError("old/new は同じ長さが必要です")
    return bytes([a^b for a,b in zip(old,new)])


def bitflip_prev_block(token:bytes,target_plain_block_index:int,offset_in_block:int,diff:bytes)->bytes:
    """
    CBCの基本:Pi を変えたいなら C_{i-1} を XOR する(i=1ならIV)
    target_plain_block_index: 0-based の平文ブロック番号 (P1=0, P2=1, ...)
    offset_in_block: 対象平文ブロック内で置換を始めるオフセット (0..15)
    diff: old XOR new のバイト列
    """
    raw= bytearray(token)

    # 直前ブロック開始位置(IVをブロック0とみなす)
    prev_block_index = target_plain_block_index
    prev_start = prev_block_index*BLOCK
    prev_end= prev_start+BLOCK

    if prev_end > len(raw):
        raise ValueError("ブロック指定がトークン長を超えています")
    if offset_in_block+len(diff) >BLOCK:
        raise ValueError("offset+len(diff)が16バイト境界を超えています")
    for i,d in enumerate(diff):
        raw[prev_start+offset_in_block+i]^=d
    
    return bytes(raw)

if __name__=="__main__":
    key=get_random_bytes(16)

    old_role = b"user"
    new_role = b"root"
    diff=xor_diff(old_role,new_role)

    plaintext = b'{"role":"user","x":"1"}'
    token =encrypt_cbc(plaintext,key)

    print("[Origin token]",token)

    offset = plaintext.index(b"user")
    target_plain_block_index =0 # P1 を狙う → 直前は IV

    modified =bitflip_prev_block(token,target_plain_block_index,offset,diff)
    print("[Modified Token]",modified)


    before = decrypt_cbc(token,key)
    after = decrypt_cbc(modified,key)

    print("[Before]",before)
    print("[After]",after)


実行結果

[Origin token] b'\x0b\xdc\x9beak;\xb4?\x89\xc2\xc7\xa9\xae\xf8\xdd\xde\xd4\xb6<\xf4I\xb2\xec\xa1l\x14}a\xd3\xb0K8\xf1\xfe\x95j=\xc9rDl\x04\x02\x9cH\x14\xab'
[Modified Token] b'\x0b\xdc\x9beak;\xb4?\x8e\xde\xcd\xaf\xae\xf8\xdd\xde\xd4\xb6<\xf4I\xb2\xec\xa1l\x14}a\xd3\xb0K8\xf1\xfe\x95j=\xc9rDl\x04\x02\x9cH\x14\xab'
[Before] b'{"role":"user","x":"1"}'
[After] b'{"role":"root","x":"1"}'

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?