溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何實現(xiàn)Java日志注入

發(fā)布時間:2021-10-12 15:34:19 來源:億速云 閱讀:150 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“如何實現(xiàn)Java日志注入”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

案例故事

某個新系統(tǒng)上線了,小A在其中開發(fā)了個簡單的登錄模塊,會在日志里記錄所有登錄成功或者失敗的用戶。

小A對用戶名都做了白名單校驗,不正確的名字,也會用WARN的形式,打印出來做記錄。

像下面這樣:

[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][WARN][main] [Login:308] username is wrong,userName=tony.dssdff

日志對接了風(fēng)險審計系統(tǒng),會定期從日志中審計出那些每天有可疑登錄行為的人,例如那些半夜登錄或者頻繁登錄(不要在意細(xì)節(jié),不用審計也能做,只是舉個例子而已)

如何實現(xiàn)Java日志注入

某天,日志審計系統(tǒng)提示tony登錄過于頻繁且高危操作, 于是把tony的號給封了。

隨后一天又封了N多個無辜的用戶,引發(fā)用戶大量不滿。運營部找來問罪,小A拿出下面的日志文件做證據(jù):

[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][WARN][main] [Login:308] username is wrong,userName=tony.dssdff
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony

然而tony反應(yīng)說他那天在外面旅游,電腦也放在家中,是有證據(jù)的。

這時候小A的老大翻出了請求接口日志,發(fā)現(xiàn)那時候有1個請求發(fā)來, 接口里的username參數(shù)竟然是:

username=tony.dssdff
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony

好家伙,竟然是username里帶了換行,雖然我做了白名單校驗,但是日志里為了記錄這個帶換行的錯誤名,坑了一堆用戶。(因為對方可能是使用rest-api去惡意發(fā)送的,所以也繞過了前臺頁面的校驗)

小A的公司因此遭遇了巨大損失,小A最終也失業(yè)了。

簡單整改方法

小A費勁九牛二虎之力找到一家新公司,接手了一堆舊代碼。他決定提前預(yù)防, 給外部輸入的日志參數(shù)加上換行處理.

他寫了一個方法如下:

    /**
     * 獲取凈化后的消息,過濾掉換行,避免日志注入
     * @param message
     * @return
     */
    public static String getCleanedMsg(String message) {
        if (message == null) {
            return "";
        }

        message = message.replace('\n', '_').replace('\r', '_');
        return message;
    }

并且給自己打日志的地方,補充了這個方法

LOGGER.warn("username is wrong,userName={}", getCleanedMsg(userName));

但是想起來這個系統(tǒng)比較舊,還有好多類似的參數(shù),于是搜索了一下,發(fā)現(xiàn)竟然有一千多處帶參數(shù)的日志,好多是前輩留給他的坑。

于是他懷著責(zé)任心一個一個修改和檢查, 花了一個多月終于把所有外部輸入的參數(shù)排查出來并加上getCLeanMsg方法。年末最終因為輸出不夠,背了個最低績效,郁郁寡歡,頭發(fā)又掉光了。

log4j2配置統(tǒng)一修改message

小A被換了個項目組,這次決定不再重蹈覆轍,使用別的方式簡化一下。他的項目里日志都是用log4j2打印的,如果能利用框架能力,把日志的換行全部去掉就好了,嚴(yán)格保證日志輸出的只有1行。

于是開始認(rèn)真學(xué)習(xí)log4j2的官方文檔。他在里面找到了和日志輸出格式有關(guān)的位置,如下: https://logging.apache.org/log4j/2.x/manual/layouts.html

他搜索\n或者換行的關(guān)鍵字,找到了如下的內(nèi)容:

如何實現(xiàn)Java日志注入

文檔里寫得很清楚, 使用%enc{%m}{CRLF}, 即可對這部分進(jìn)行換行的過濾處理。于是在log4j2.xml的<PatternLayout>改成了如下:

        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5p] [%t] [%c{10}#%M:%L] %enc{%m}{CRLF} %n "/>
        </Console>

測試,最終所有的日志都會只有一行。以前會引發(fā)問題的日志也變成了

username=tony.dssdff\r\n[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony

因此不會被日志系統(tǒng)錯誤解析,同時也省去了一個個排查的風(fēng)險。

log4j2 修改異常里的mesage

過了一個月,突然日志審計又告警了, 最終排查下來又是誤報。去看了日志,發(fā)現(xiàn)長這樣:

[2021-04-17 16:50:35][INFO][main] [Login:308] unknown error happend
java.lang.RuntimeException: name,name=%s
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
        at java.net.SocketInputStream.socketRead0(Native Method) ~[?:?]
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:115) ~[?:?]
        at java.net.SocketInputStream.read(SocketInputStream.java:168) ~[?:?]
        at java.net.SocketInputStream.read(SocketInputStream.java:140) ~[?:?]
        at sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:448) ~[?:?]

好家伙,原來是有些地方打印日志時, 順便把未處理過的異常堆棧也打印出來了。異常堆棧的第一行往往是異常名+message, 這里也能被惡意攻擊。

小A翻遍了log4j2文檔,沒有找到能在異常中處理換行的符號,只找到了1個ThrowablePatternConverter, 文檔里告訴他,你可以自定義這個ThrowablePatternConverter,來打印自己想要的異常。

于是他自己編寫了一個UndefineThrowablePatternConvert,在里面重寫了日志堆棧打印的邏輯,

/**
 * 會對異常做特定編碼處理的格式轉(zhuǎn)換類
 * 使用時,在layout中添加 %eEx即可
 *
 * @since 2021/4/16
 */
@Plugin(name = "UndefineThrowablePatternConverter", category = PatternConverter.CATEGORY)
// 自己定義的layout鍵值
@ConverterKeys({"uEx"})
public class UndefineThrowablePatternConverter extends ThrowablePatternConverter {
 
      /**
     * 進(jìn)行過特定編碼處理的ThrowableProxy
     */
    static class EncodeThrowableProxy extends ThrowableProxy {
        public EncodeThrowableProxy(Throwable throwable) {
            super(throwable);
        }

        // 將\r和\n進(jìn)行編碼,避免日志注入
        @Override
        public String getMessage() {
            String encodeMessage = super.getMessage().replaceAll("\r", "\\\\r").replaceAll("\n", "\\\\n");
            return encodeMessage;
        }
    }
 
    protected UndefineThrowablePatternConverter(Configuration config, String[] options) {
        super("UndefineThrowable", "throwable", options, config);
    }
 
      // log4j2中使用反射調(diào)用newInstance靜態(tài)方法進(jìn)行構(gòu)造,因此必須要實現(xiàn)這個方法。
    public static UndefineThrowablePatternConverter newInstance(final Configuration config, final String[] options) {
        return new UndefineThrowablePatternConverter(config, options);
    }

    @Override
    public void format(final LogEvent event, final StringBuilder toAppendTo) {
          Throwable throwable = event.getThrown();
        if (throwable == null) {
            return;
        }
        // 使用自定義的EncodeThrowableProxy,里面重寫了ThrowableProxy的getMessage方法
        EncodeThrowableProxy proxy = new EncodeThrowableProxy(throwable);
         // 添加到toAppendTo
          proxy.formatExtendedStackTraceTo(toAppendTo, options.getIgnorePackages(), options.getTextRenderer(), getSuffix(event), options.getSeparator());
    }
}

并且在PatternLayout中添加%uEx, 就會使用這里的format去生成堆棧字符串。

“如何實現(xiàn)Java日志注入”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI