Неверный ввод вызывает обход аутентификации в системе обмена сообщениями 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

1 ответ

Решение

известная ошибка в 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 для запросов уровня администратора, чтобы все, что получает через или через ваш прокси-сервер все равно нужно знать какой-то ключ.

Другие вопросы по тегам