Тема сегодняшней статьи будет касаться XML поскольку сегодня (10 февраля) XML-ю исполнилось 16 лет!
XML и Java дружат очень давно и в интернете можно найти множество учебных материалов посвященных парсингу XML-файлов, но не смотря на это, время от времени встречаю в коде разных программистов некоторые не совсем удачные решения при разборе XML.
Эта статья возможно не будет интересна тем, кто занимается подобными задачами из года в год и парсит XML-ки с закрытами глазами, но возможно для тех кто только начал вникать в задачу, эти пару прописных истин будут полезны.
Итак.
1. Поиск нужных элементов.
Метод Element.getElementsByTagName возвращает список всех подэлементов с указаным именем.
Всех значит прямо всех, не только непосредственных “детей” этого элементов, но и “внуков” (детей их детей) и “правнуков” и т.д.
Другими словами, если у нас есть следующий XML:
one
two
Тогда при его обработке:
Element element = ... ; // каким-то образом получили element
NodeList nodeList = element.getElementsByTagName("string");
int length = nodeList.getLength();
for (int i=0;i
Вернутся элемент "one" и элемент "two".
По моим наблюдениям, наиболее часто такую не очень оптимальную обработку XML-файлов используют Java программисты при разборе iOS-ных plist-ов, в итоге разбор скорость разбора файла сильно замедляется.
2. Безопасный парсинг.
Предположим вы принимаете в качестве запроса XML-файлики вида:
1
10.02.2014
hello world
Тогда если злоумышленник отправит вам вместо этого:
]>
1
10.02.2014
&xxe;
Тогда содержимое элемента "message" будет список файлов с вашего диска С (ничто не мешает указать не директорию, а запросить содержимое файла, например: SYSTEM "file:///etc/password").
Это возможно например в случае если код обработки XML-ки выглядит приблизительно следующим образом:
// упрощенный пример
File f = new File("xxe.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(f);
Element root = document.getDocumentElement();
// для простоты сразу берем message
Element message = (Element) root.getElementsByTagName("message").item(0);
String textContent = message.getTextContent(); // тоже для упрощения
System.out.println(textContent);
Это не очень приятно, т.к. гипотетически возможны следующие сценарии дальнейшего развития события:
1. Ваш серверный код умный, и тогда вернет ошибку некорректное значение такое-то:"содержимое файла" и злоумышленник получит результат сразу.
2. Ваш код не делает никаких проверок, сохраняет запись. Тогда злоумышленник получит данные потом, запросив содержимое документа.
Самое главное, что это не какая-то ошибка, а вполне законная работа API, которая так и должна работать и до сих пор работает из коробки в Java 7.
Такой способ взлома уже используется наверное больше десяти лет, но программисты продолжают его игнорировать.
Для того, чтобы обезопасится, нужно предпринять некоторые усилия - указать дополнительные настройки XML-парсера, ограничить права доступа и т.д.
Какие конкретно настройки указывать можно найти с помощью поисковика по запросу "XML External Entity" или посмотреть на сайте OWASP.
Надеюсь эти пару советов помогут многим Java программистам, которые начинают заниматься разбором XML не делать лишних ошибок.