본문 바로가기
Server

image file 속에 web shell (웹쉘) 찾기.

by 모닝위즈 2023. 11. 19.
반응형

ClamAV, 알약, V3, Avast 모두 web shell을 찾지는 못하였다..

그래서, 그냥 직접 만들자. 그러면 기본적인 부분은 찾을 수 있겠다 싶어서 직접 만들어서 찾아보도록 했다.

그런데 대략 300만개 이미지 파일을 검사를 하는데에 시간이 오래걸리긴 했지만...

5개가 검출되었고, php 코드가 숨겨져있었다.

 

기본적인 소스코드는 아래와 같다.

import java.io.*;

/**
 * ImageFileInWebShellChecker : 파라미터로 받은 경로를 기반으로 이미지 파일을 찾고 이미지 속에 웹쉘이 존재하는지 체크하는 클래스
 *
 * @author mitw
 * @version 1.0
 */
public class ImageFileInWebShellChecker {

    public static void main(String[] args) {

        if (args.length != 1) {
            System.out.println("Usage: java RecursiveCodeSecurityChecker <directory_path>");
            System.exit(1);
        }

        try {
            String directoryPath = args[0];
            checkFilesInDirectory(new File(directoryPath));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static void checkFilesInDirectory(File directory) throws IOException {
        if (directory.isDirectory()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        // 하위 디렉토리 탐색
                        checkFilesInDirectory(file);
                    } else {
                        // 파일 검사
                        checkFile(file);
                    }
                }
            }
        }
    }

    private static void checkFile(File file) throws IOException {
        System.out.println("Checking: " + file.getAbsolutePath());

        if(! isImageFile(file)) {
            if (file.getName().toLowerCase(Locale.ROOT).contains("png")
                    || file.getName().toLowerCase(Locale.ROOT).contains("jpg")
                    || file.getName().toLowerCase(Locale.ROOT).contains("gif")
                    || file.getName().toLowerCase(Locale.ROOT).contains("jpeg")
                    || file.getName().toLowerCase(Locale.ROOT).contains("bmp")
                    || file.getName().toLowerCase(Locale.ROOT).contains("tiff")
                    || file.getName().toLowerCase(Locale.ROOT).contains("tif")
                    || file.getName().toLowerCase(Locale.ROOT).contains("jfif")
            ) {
                System.out.println(">>>> danger image file. There is a risk web shell or broken file !!! ");
            } else {
                System.out.println(">>>> No image file ... ");
            }
        } else {
            if(containsWebShell(file)) {
                System.out.println(">>>> danger image file. There is a risk web shell !!! ");
            } else {
                System.out.println(">>>> not danger image file. ");
            }
        }

    }

    private static boolean isImageFile(File file) throws IOException {
        try (FileInputStream inputStream = new FileInputStream(file)) {
            byte[] header = new byte[8];
            inputStream.read(header);
            return isImageType(header);
        }
    }

    //jpeg, png, gif, bmp, tiff, jfif   헤더 체크
    private static boolean isImageType(byte[] header) {
        return (header[0] == (byte) 0xFF && header[1] == (byte) 0xD8 && header[2] == (byte) 0xFF)
                || (header[0] == (byte) 0x89 && header[1] == (byte) 0x50 && header[2] == (byte) 0x4E && header[3] == (byte) 0x47)
                || (header[0] == (byte) 0x47 && header[1] == (byte) 0x49 && header[2] == (byte) 0x46)
                || (header[0] == (byte) 0x42 && header[1] == (byte) 0x4D)
                || (header[0] == (byte) 0x4D && header[1] == (byte) 0x4D) || (header[0] == (byte) 0x49 && header[1] == (byte) 0x49)
                || (header[0] == (byte) 0x4A && header[1] == (byte) 0x46 && header[2] == (byte) 0x49 && header[3] == (byte) 0x46);
    }


    private static boolean containsWebShell(File file) throws IOException {
        try (FileInputStream inputStream = new FileInputStream(file)) {
            byte[] buffer = new byte[1024];
            int bytesRead;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                String content = new String(buffer, 0, bytesRead);

                // PHP 코드 패턴 검사
                if (content.contains("<?php")) {
                    System.out.println(">>> PHP");
                    System.out.println(">>> " + content);
                    return true;
                }

                // JSP 코드 패턴 검사
                if (content.contains("<%@ ") || content.contains("<jsp:")) {
                    System.out.println(">>> JSP");
                    System.out.println(">>> " + content);
                    return true;
                }

                // JAVASCRIPT 코드 패턴 검사
                if (content.contains("eval(") || content.contains("<script")) {
                    System.out.println(">>> script");
                    System.out.println(">>> " + content);
                    return true;
                }

                // JSP 코드 패턴 검사
                if (content.contains("ASP.NET")) {
                    System.out.println(">>> ASP.NET");
                    System.out.println(">>> " + content);
                    return true;
                }

            }
        }
        return false;
    }
}

 

위 내용을 가지고 intellij 로 jar를 만들어서 실행을 하였다.

 

아래와 같이 jar를 만들어서 실행하면 된다.

java -jar ./ImageFileInWebShellChecker.jar <path>

ex> java -jar ./ImageFileInWebShellChecker.jar C:\Users\Kks\Pictures\image

 

아래는 테스트성 검사를 진행한 부분이다.

C:\work>java -jar ./ImageFileInWebShellChecker.jar C:\Users\Kks\Pictures\image
Checking: C:\Users\Kks\Pictures\image\0b6936bec8ee32bf3e381e9000e1ded6--black-letter-letter-k.jpg
>>>> not danger image file.
Checking: C:\Users\Kks\Pictures\image\musiciocom.jpg
>>>> not danger image file.
Checking: C:\Users\Kks\Pictures\image\sx.jpg
>>>> not danger image file.

 

서버에서 검출된 내용의 경우, 실무에서 사용한 부분이라서 보안을 위해 공개를 하지 않겠음.

 

실무에서 검출된 내용은 읍읍..

 

댓글