Skip to content

Commit 3f3742a

Browse files
committed
json-parser-zap: unfold instances of each alert
... so that we can match each instance separately Fixes: #90 Closes: #94
1 parent 933decc commit 3f3742a

File tree

4 files changed

+199
-64
lines changed

4 files changed

+199
-64
lines changed

src/lib/parser-json-zap.cc

+92-58
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,22 @@
2121

2222
struct ZapTreeDecoder::Private {
2323
std::string timeStamp;
24-
Defect defPrototype = Defect("OWASP_ZAP_WARNING");
24+
Defect sitePrototype;
25+
Defect alertPrototype;
2526
const pt::ptree *alertList = nullptr;
27+
const pt::ptree *instList = nullptr;
2628
pt::ptree::const_iterator alertIter;
29+
pt::ptree::const_iterator instIter;
2730

2831
Private()
2932
{
30-
this->defPrototype.tool = "owasp-zap";
33+
this->sitePrototype.checker = "OWASP_ZAP_WARNING";
34+
this->sitePrototype.tool = "owasp-zap";
3135
}
3236

33-
void readAlert(Defect *pDef, const pt::ptree &alertNode);
37+
void readSiteProto(const pt::ptree &siteNode);
38+
void readAlertProto(const pt::ptree &alertNode);
39+
void readAlertInst(Defect *pDef, const pt::ptree &instNode);
3440
};
3541

3642
template <typename TPropList>
@@ -51,33 +57,35 @@ void readNonEmptyProps(
5157
}
5258
}
5359

54-
void ZapTreeDecoder::Private::readAlert(Defect *pDef, const pt::ptree &alertNode)
60+
void ZapTreeDecoder::Private::readSiteProto(const pt::ptree &siteNode)
5561
{
56-
// read per-defect properties
57-
*pDef = this->defPrototype;
58-
pDef->cwe = valueOf<int>(alertNode, "cweid");
59-
pDef->imp = (1 < valueOf<int>(alertNode, "riskcode"));
60-
61-
// get "uri" for the key event
62-
std::string uri;
63-
const pt::ptree *instList = nullptr;
64-
if (findChildOf(&instList, alertNode, "instances")) {
65-
for (const auto &item : *instList) {
66-
uri = valueOf<std::string>(item.second, "uri");
67-
if (!uri.empty())
68-
// found!
69-
break;
70-
}
71-
}
62+
this->sitePrototype.events.clear();
63+
const auto siteName = valueOf<std::string>(siteNode, "@name");
64+
if (siteName.empty() || this->timeStamp.empty())
65+
return;
7266

73-
TEvtList &events = pDef->events;
74-
if (uri.empty() && !events.empty())
75-
// fallback to "uri" from the prototype event
76-
uri = events.front().fileName;
67+
// create a prototype "note" event
68+
DefEvent siteEvt("note");
69+
siteEvt.fileName = std::move(siteName);
70+
siteEvt.msg = "dynamically analyzed on " + this->timeStamp;
71+
siteEvt.verbosityLevel = /* info event */ 1;
72+
this->sitePrototype.events.push_back(std::move(siteEvt));
73+
}
74+
75+
void ZapTreeDecoder::Private::readAlertProto(const pt::ptree &alertNode)
76+
{
77+
// read per-alert properties
78+
this->alertPrototype = this->sitePrototype;
79+
this->alertPrototype.cwe = valueOf<int>(alertNode, "cweid");
80+
this->alertPrototype.imp = (1 < valueOf<int>(alertNode, "riskcode"));
7781

7882
// initialize key event
7983
DefEvent evt("alert");
80-
evt.fileName = uri;
84+
85+
// get "uri" from the prototype event
86+
TEvtList &events = this->alertPrototype.events;
87+
if (!events.empty())
88+
evt.fileName = events.front().fileName;
8189

8290
// read "alertRef" if available
8391
const auto alertRef = valueOf<std::string>(alertNode, "alertRef");
@@ -88,29 +96,36 @@ void ZapTreeDecoder::Private::readAlert(Defect *pDef, const pt::ptree &alertNode
8896
evt.msg = valueOf<std::string>(alertNode, "alert");
8997

9098
// append the key event
91-
pDef->keyEventIdx = events.size();
99+
this->alertPrototype.keyEventIdx = events.size();
92100
events.push_back(evt);
93101

94102
// read other per-alert events if available
95103
evt.verbosityLevel = /* info event */ 1;
96104
const auto defProps = { "desc", "solution", "otherinfo", "reference" };
97105
readNonEmptyProps(&events, alertNode, evt, defProps);
106+
}
98107

99-
if (!instList)
100-
// no instances to go through
101-
return;
108+
void ZapTreeDecoder::Private::readAlertInst(
109+
Defect *pDef,
110+
const pt::ptree &instNode)
111+
{
112+
// start with the prototype initialized by readAlertProto()
113+
*pDef = this->alertPrototype;
114+
TEvtList &events = pDef->events;
115+
116+
// reinitialize events with "uri" specific for this instance (if available)
117+
const std::string uri = valueOf<std::string>(instNode, "uri");
118+
if (!uri.empty())
119+
for (DefEvent &evt : events)
120+
evt.fileName = uri;
121+
122+
// use the key event as a prototype for instance-specific events
123+
DefEvent evtProto = events[pDef->keyEventIdx];
124+
evtProto.verbosityLevel = /* info event */ 1;
102125

103126
// read per-instance properties
104127
const auto instProps = { "method", "param", "attack", "evidence" };
105-
for (const auto &item : *instList) {
106-
const pt::ptree &instNode = item.second;
107-
evt.fileName = valueOf<std::string>(instNode, "uri");
108-
if (evt.fileName.empty())
109-
// no "uri" for this instance
110-
continue;
111-
112-
readNonEmptyProps(&events, instNode, evt, instProps);
113-
}
128+
readNonEmptyProps(&events, instNode, evtProto, instProps);
114129
}
115130

116131
ZapTreeDecoder::ZapTreeDecoder():
@@ -131,14 +146,14 @@ void ZapTreeDecoder::readScanProps(
131146
d->timeStamp = valueOf<std::string>(*root, "@generated");
132147
}
133148

134-
bool ZapTreeDecoder::readNode(Defect *pDef)
149+
const pt::ptree* ZapTreeDecoder::nextAlert()
135150
{
136151
// iterate over sites unless we are processing a site already
137152
while (!d->alertList || d->alertList->end() == d->alertIter) {
138153
const pt::ptree *siteNode = this->nextNode();
139154
if (!siteNode)
140155
// failed initialization or EOF
141-
return false;
156+
return nullptr;
142157

143158
if (!findChildOf(&d->alertList, *siteNode, "alerts")) {
144159
// "alerts" node missing for this site
@@ -148,29 +163,48 @@ bool ZapTreeDecoder::readNode(Defect *pDef)
148163

149164
// initialize iteration over alerts
150165
d->alertIter = d->alertList->begin();
166+
d->instList = nullptr;
151167

152-
if (d->alertList->end() != d->alertIter) {
153-
// site with alerts found -> update defect prototype based on site
154-
d->defPrototype.events.clear();
155-
const auto siteName = valueOf<std::string>(*siteNode, "@name");
156-
if (!siteName.empty() && !d->timeStamp.empty()) {
157-
// create a prototype "note" event
158-
DefEvent siteEvt("note");
159-
siteEvt.fileName = std::move(siteName);
160-
siteEvt.msg = "dynamically analyzed on " + d->timeStamp;
161-
siteEvt.verbosityLevel = /* info event */ 1;
162-
d->defPrototype.events.push_back(std::move(siteEvt));
163-
}
164-
165-
break;
166-
}
168+
if (!d->alertList->empty())
169+
// site with alerts found --> update site prototype
170+
d->readSiteProto(*siteNode);
167171
}
168172

169173
// get the current alert and move to the next one
170-
const auto itNow = d->alertIter++;
174+
const auto itAlertNow = d->alertIter++;
175+
return &itAlertNow->second;
176+
}
177+
178+
bool ZapTreeDecoder::readNode(Defect *pDef)
179+
{
180+
if (!d->instList || d->instList->end() == d->instIter) {
181+
// iterate over alerts
182+
const pt::ptree *alertNode = this->nextAlert();
183+
if (!alertNode)
184+
// failed initialization or EOF
185+
return false;
186+
187+
// process the current alert
188+
d->readAlertProto(*alertNode);
189+
190+
// read the list of instances
191+
if (!findChildOf(&d->instList, *alertNode, "instances")
192+
|| d->instList->empty())
193+
{
194+
// no instances for this alert --> emit the prototype
195+
d->instList = nullptr;
196+
*pDef = d->alertPrototype;
197+
return true;
198+
}
199+
200+
// initialize iteration over instances
201+
d->instIter = d->instList->begin();
202+
}
171203

172-
// process the current alert
173-
d->readAlert(pDef, itNow->second);
204+
// get the current instance and move to the next one
205+
const auto itInstNow = d->instIter++;
174206

207+
// process the current instance
208+
d->readAlertInst(pDef, itInstNow->second);
175209
return true;
176210
}

src/lib/parser-json-zap.hh

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class ZapTreeDecoder: public AbstractTreeDecoder {
3838
private:
3939
struct Private;
4040
std::unique_ptr<Private> d;
41+
const pt::ptree* nextAlert();
4142
};
4243

4344
#endif /* H_GUARD_PARSER_JSON_ZAP_H */

tests/csgrep/0103-json-parser-zap-stdout.txt

+103-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"key_event_idx": 1,
1111
"events": [
1212
{
13-
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000",
13+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/id/pet_id",
1414
"line": 0,
1515
"event": "note",
1616
"message": "dynamically analyzed on Tue, 9 Aug 2022 14:38:31",
@@ -60,7 +60,7 @@
6060
"key_event_idx": 1,
6161
"events": [
6262
{
63-
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000",
63+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/id/pet_id",
6464
"line": 0,
6565
"event": "note",
6666
"message": "dynamically analyzed on Tue, 9 Aug 2022 14:38:31",
@@ -110,7 +110,7 @@
110110
"key_event_idx": 1,
111111
"events": [
112112
{
113-
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000",
113+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/docs/openapi.json",
114114
"line": 0,
115115
"event": "note",
116116
"message": "dynamically analyzed on Tue, 9 Aug 2022 14:38:31",
@@ -164,6 +164,56 @@
164164
"event": "param",
165165
"message": "X-Content-Type-Options",
166166
"verbosity_level": 1
167+
}
168+
]
169+
},
170+
{
171+
"checker": "OWASP_ZAP_WARNING",
172+
"cwe": 693,
173+
"tool": "owasp-zap",
174+
"key_event_idx": 1,
175+
"events": [
176+
{
177+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/",
178+
"line": 0,
179+
"event": "note",
180+
"message": "dynamically analyzed on Tue, 9 Aug 2022 14:38:31",
181+
"verbosity_level": 1
182+
},
183+
{
184+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/",
185+
"line": 0,
186+
"event": "alert[10021]",
187+
"message": "X-Content-Type-Options Header Missing",
188+
"verbosity_level": 0
189+
},
190+
{
191+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/",
192+
"line": 0,
193+
"event": "desc",
194+
"message": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
195+
"verbosity_level": 1
196+
},
197+
{
198+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/",
199+
"line": 0,
200+
"event": "solution",
201+
"message": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
202+
"verbosity_level": 1
203+
},
204+
{
205+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/",
206+
"line": 0,
207+
"event": "otherinfo",
208+
"message": "<p>This issue still applies to error type pages (401, 403, 500, etc.) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scan rule will not alert on client or server error responses.</p>",
209+
"verbosity_level": 1
210+
},
211+
{
212+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/",
213+
"line": 0,
214+
"event": "reference",
215+
"message": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://owasp.org/www-community/Security_Headers</p>",
216+
"verbosity_level": 1
167217
},
168218
{
169219
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/",
@@ -178,6 +228,56 @@
178228
"event": "param",
179229
"message": "X-Content-Type-Options",
180230
"verbosity_level": 1
231+
}
232+
]
233+
},
234+
{
235+
"checker": "OWASP_ZAP_WARNING",
236+
"cwe": 693,
237+
"tool": "owasp-zap",
238+
"key_event_idx": 1,
239+
"events": [
240+
{
241+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/name/pet_name",
242+
"line": 0,
243+
"event": "note",
244+
"message": "dynamically analyzed on Tue, 9 Aug 2022 14:38:31",
245+
"verbosity_level": 1
246+
},
247+
{
248+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/name/pet_name",
249+
"line": 0,
250+
"event": "alert[10021]",
251+
"message": "X-Content-Type-Options Header Missing",
252+
"verbosity_level": 0
253+
},
254+
{
255+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/name/pet_name",
256+
"line": 0,
257+
"event": "desc",
258+
"message": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
259+
"verbosity_level": 1
260+
},
261+
{
262+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/name/pet_name",
263+
"line": 0,
264+
"event": "solution",
265+
"message": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
266+
"verbosity_level": 1
267+
},
268+
{
269+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/name/pet_name",
270+
"line": 0,
271+
"event": "otherinfo",
272+
"message": "<p>This issue still applies to error type pages (401, 403, 500, etc.) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scan rule will not alert on client or server error responses.</p>",
273+
"verbosity_level": 1
274+
},
275+
{
276+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/name/pet_name",
277+
"line": 0,
278+
"event": "reference",
279+
"message": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://owasp.org/www-community/Security_Headers</p>",
280+
"verbosity_level": 1
181281
},
182282
{
183283
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:5000/pets/name/pet_name",

tests/csgrep/0105-json-parser-zap-stdout.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"key_event_idx": 1,
1212
"events": [
1313
{
14-
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:9000",
14+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:9000/api/v1/activities/",
1515
"line": 0,
1616
"event": "note",
1717
"message": "dynamically analyzed on Wed, 10 Aug 2022 10:13:02",
@@ -90,7 +90,7 @@
9090
"key_event_idx": 1,
9191
"events": [
9292
{
93-
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:9000",
93+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:9000/api/v1/activities/",
9494
"line": 0,
9595
"event": "note",
9696
"message": "dynamically analyzed on Wed, 10 Aug 2022 10:13:02",
@@ -161,7 +161,7 @@
161161
"key_event_idx": 1,
162162
"events": [
163163
{
164-
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:9000",
164+
"file_name": "http://rhos-fedora-devel.usersys.redhat.com:9000/api/v1/activities/",
165165
"line": 0,
166166
"event": "note",
167167
"message": "dynamically analyzed on Wed, 10 Aug 2022 10:13:02",

0 commit comments

Comments
 (0)