Неверные данные вызывают обход аутентификации в системе обмена сообщениями JSON полиглота?

Это очень простая система, основанная на отправке сообщений JSON, которая, похоже, имеет уязвимость безопасности. Существует сервер Python (использующий модуль JSON, включенный в стандартную библиотеку), который получает объекты JSON и воздействует на них. Если это получится {"req": "ping"}просто возвращается {"resp": "pong"}, Также есть команда для настройки громкости и одна для изменения пароля администратора. Администратор может отправить любой JSON на этот сервер. Вот это (server.py):

import json
import sys

def change_admin_password(p): pass # empty for test
def set_volume(v): pass # empty for test

def handle(js):
if (js["req"] == "ping"):
return {"resp": "pong"}
if (js["req"] == "change admin password"):
change_admin_password(js["args"]["password"])
return {"resp": "ok"}
if (js["req"] == "set volume"):
set_volume(int(js["args"]["volume"]))
return {"resp": "ok"}

print handle(json.load(sys.stdin))

Он читает команду из стандартного ввода и обрабатывает ее. Другой сценарий считывает объекты JSON из сетевого сокета и передает их этому сценарию.

Другие пользователи должны пройти через прокси, написанный на C ++, используя libjson. Он просто блокирует запросы, которые должны требовать прав администратора. Например, если пользователь пытается изменить пароль администратора, прокси-сервер отклонит команду:

$ echo '{"req": "change admin password", "args": {"password":"new"}}' | ./proxy
echo $?
1

Вот код (proxy.cpp):

#include "libjson.h"using namespace std;
int main() {
string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
JSONNode n = libjson::parse(json);
string req = n.at("req").as_string();
if (req == "change admin password") {
return 1;
}
cout << n.write();
}

Чтобы использовать прокси, главный скрипт, управляющий сокетом, будет передавать данные через прокси и выводить их на сервер Python:

$ echo '{"req": "ping"}' | ./proxy | python server.py
{'resp': 'pong'}
$ echo '{"req": "set volume", "args": {"volume": 50}}' | ./proxy | python server.py
{'resp': 'ok'}

И если пользователь попытается выполнить команду с ограниченным доступом, она завершится неудачно, как и ожидалось:

$ echo '{"req": "change admin password", "args": {"password": "new"}}' | ./proxy | python server.py
Traceback (most recent call last):
File "server.py", line 17, in <module>
[...]

Но по какой-то причине, если ключ «req» находится в JSON дважды (не должен ли он быть незаконным?), Пользователи без прав администратора могут изменить пароль администратора:

$ echo '{"req": "nothing", "req": "change admin password", "args": {"password": "new"}}' | ./proxy | python server.py
{'resp': 'ok'}

Зачем?


Попытка обходного пути Майка МакМахона:

Я пытался с помощью JSONNode.find как обходной путь, но, похоже, не работает.

Я попытался просто перебрать все элементы:

#include "libjson.h"using namespace std;
int main() {
string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
JSONNode n = libjson::parse(json);
for (JSONNode::json_iterator it = n.begin(); it != n.end(); it++) {
cout << "found one: " << it->at("x").as_string() << endl;
}
}

Это работает:

$ g++ proxy.cpp libjson.a  -o proxy && ./proxy
In file included from libjson.h:4:0,
from proxy.cpp:1:
_internal/Source/JSONDefs.h:157:6: warning: #warning , Release build of libjson, but NDEBUG is not on [-Wcpp]
{"a": {"x": 1}, "b": {"x": 2}}
found one: 1
found one: 2

Кроме этого segfaults, если JSON является недействительным? Я неправильно использую итератор?

$ echo '{"a": {"x": 1}, {"b": {"x": 2}}' | ./proxy
found one: 1
Segmentation fault

Я заменил n.begin() с n.find("y"):

#include "libjson.h"using namespace std;
int main() {
string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
JSONNode n = libjson::parse(json);
for (JSONNode::json_iterator it = n.find("y"); it != n.end(); it++) {
cout << "found one: " << it->at("x").as_string() << endl;
}
}

Это не работает вообще. Я неправильно использую итератор?

g++ proxy.cpp libjson.a  -o proxy && ./proxy
In file included from libjson.h:4:0,
from proxy.cpp:1:
_internal/Source/JSONDefs.h:157:6: warning: #warning , Release build of libjson, but NDEBUG is not on [-Wcpp]
{"y": {"x": 1}}
Segmentation fault

Еще одна попытка обходного пути:

#include "libjson.h"using namespace std;
int main() {
string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
JSONNode n = libjson::parse(json);
for (JSONNode::json_iterator it = n.begin(); it != n.end(); it++) {
if (it->name() == "req" && it->as_string() == "change admin password")
{
cout << "found one " << endl;
}
}
}

Оно работает!

$ echo '{"req": "change admin password"}' | ./proxy
found one
$ echo '{"req": "x", "req": "change admin password"}' | ./proxy
found one
$ echo '{"req": "change admin password", "req": "x"}' | ./proxy
found one

Но все еще segfaults с неправильным вводом JSON?

$ echo '{"req": "x", {"req": "x"}' | ./proxy
Segmentation fault

2

Решение

известная ошибка в libjson http://sourceforge.net/p/libjson/bugs/47/


Рассмотрим JSON:

{ "same" : 4, "same": 5}

если вы нажмете в JSON, вы увидите ошибку (дубликаты имен ключей)

если вы нажмете в JSON, вы будете НЕ увидеть ошибку, но JSON будет переформатирует и удалит первый «такой же» ключ.

Краткий обзор показывает, что в стандарте говорится, что у вас «не должно» быть дублированных имен ключей, а не «не должно», так что технически libjson в порядке.

Ваш API (т. Е. At (string), operator) не соответствует ни одному из этих двух сайтов (см. Выше).

  • jslint показывает ошибку
  • jsonlint убивает первый узел, оставляя узел со значением 5, что, я полагаю, согласуется с переопределением поведения javascript.

функция libjson.at (string) будет возвращать ПЕРВУЮ запись (а не последнюю).

Обходной путь с JSONNode.find («req»)

Посмотрите на метод поиска

JSONNode.find("req");

Возвращает указатель на массив узлов, который можно использовать, чтобы определить, был ли он указан более одного раза. Хотя библиотека должна перезаписывать каждую запись поверх следующей (и это ошибка чтобы не делать этого, согласно действительному JSON), вы можете использовать метод find, чтобы найти и получить итератор для перехода по соответствующим узлам.

JSONNode n = libjson::parse(json)
JSONNode::json_iterator it = n.find("req");
// iterate over the array
if (it[i].at("req").as_string() == "change admin password") {
return 1;
}

нота это не лучший обходной путь в мире, так как я мог бы предположить, что он возвращает все узлы, соответствующие аргументу, и в сложной структуре может быть более одного узла с таким же именем, вложенного в другое место, для ваших нужд этого должно быть достаточно.

тем не мение вам следует заглянуть в другую библиотеку для проверки ваших вызовов JSON, если это критично, или, возможно, для поддержки отличительной команды администратора, такой как areq для административного запроса. Это позволит вам удалить любые запросы на прокси-сервере, которые содержат команду areq, поскольку только администраторы смогут генерировать такой запрос (и, очевидно, не отправлять через прокси-сервер).

Любые стандартные запросы, содержащие административные команды, будут просто сбой.

Обновить

Попробуйте использовать итератор, так как, по общему признанию, C ++ не является моим основным языком, и я не очень хорошо разбираюсь в итераторах STL.

for (JSONNode::json_iterator jsonIter = n.begin(); jsonIter != n.end(); jsonIter++) {
if (jsonIter->name() == "req" &&
jsonIter->as_string() == "change admin password")
{
// found something do magic here
}
}

Вышеупомянутый код работал для меня в моем тестировании.


незаконно или нет, вам нужно очистить для этого в прокси и на вашем сервере. он злоупотребляет вероятной уязвимостью в большинстве библиотек, соединяющих ключи вниз (поскольку у вас не может быть дубликатов ключей). Ищите его и очищайте его везде, где он не рассматривается как объект JSON.

ИЛИ поместите другой слой перед вашим прокси, который берет JSON и запускает его через синтаксический анализатор, который удалит все дублирующиеся ключи.

РЕДАКТИРОВАТЬ

JSON lib в Python получает первую защиту от req и применяя его содержание, то после второго определения req он перезаписывает первую и устанавливает команды эксплуататоров.

Поскольку ваше приложение на python вслепую предполагает, что любой входящий запрос был предварительно очищен (плохая идея), вам нужно очистить его для этого на уровне прокси и, возможно, рассмотреть возможность использования ключа API для запросов уровня администратора, чтобы все, что получает через или через ваш прокси-сервер все равно нужно знать какой-то ключ.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]