Тема сегодняшней статьи будет касаться XML поскольку сегодня (10 февраля) XML-ю исполнилось 16 лет!
XML и Java дружат очень давно и в интернете можно найти множество учебных материалов посвященных парсингу XML-файлов, но не смотря на это, время от времени встречаю в коде разных программистов некоторые не совсем удачные решения при разборе XML.
Эта статья возможно не будет интересна тем, кто занимается подобными задачами из года в год и парсит XML-ки с закрытами глазами, но возможно для тех кто только начал вникать в задачу, эти пару прописных истин будут полезны.
Итак.
1. Поиск нужных элементов.
Метод Element.getElementsByTagName возвращает список всех подэлементов с указаным именем.
Всех значит прямо всех, не только непосредственных «детей» этого элементов, но и «внуков» (детей их детей) и «правнуков» и т.д.
Другими словами, если у нас есть следующий XML:
<root> <element> <string>one</string> <foo> <moo> <string>two</string> </moo> </foo> </element> </root> |
Тогда при его обработке:
Element element = ... ; // каким-то образом получили element NodeList nodeList = element.getElementsByTagName("string"); int length = nodeList.getLength(); for (int i=0;i<length;++i) { Element el = (Element) nodeList.item(i); System.out.println(el.getTagName() +":" + el.getTextContent()); } |
Вернутся элемент «one» и элемент «two».
По моим наблюдениям, наиболее часто такую не очень оптимальную обработку XML-файлов используют Java программисты при разборе iOS-ных plist-ов, в итоге разбор скорость разбора файла сильно замедляется.
2. Безопасный парсинг.
Предположим вы принимаете в качестве запроса XML-файлики вида:
<?xml version="1.0" encoding="UTF-8"?> <doc> <id>1</id> <date>10.02.2014</date> <message>hello world</message> </doc> |
Тогда если злоумышленник отправит вам вместо этого:
<!DOCTYPE doc [ <!ENTITY xxe SYSTEM "file:///C:/" > ]> <doc> <id>1</id> <date>10.02.2014</date> <message>&xxe;</message> </doc> |
Тогда содержимое элемента «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 не делать лишних ошибок.