Рубрики
Java

Java и парсинг XML

Тема сегодняшней статьи будет касаться 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 не делать лишних ошибок.

Отметить на Facebook-e:


Оставить комментарий: