1919 */
2020package org .zaproxy .addon .authhelper ;
2121
22+ import java .net .URLEncoder ;
23+ import java .nio .charset .StandardCharsets ;
24+ import java .sql .Connection ;
25+ import java .sql .PreparedStatement ;
26+ import java .sql .ResultSet ;
27+ import java .sql .SQLException ;
2228import java .util .ArrayList ;
2329import java .util .List ;
2430import java .util .Optional ;
31+ import org .apache .commons .text .StringEscapeUtils ;
2532import org .apache .logging .log4j .LogManager ;
2633import org .apache .logging .log4j .Logger ;
34+ import org .parosproxy .paros .control .Control .Mode ;
2735import org .parosproxy .paros .core .scanner .Alert ;
2836import org .parosproxy .paros .db .DatabaseException ;
37+ import org .parosproxy .paros .db .paros .ParosDatabaseServer ;
38+ import org .parosproxy .paros .extension .SessionChangedListener ;
2939import org .parosproxy .paros .extension .history .ExtensionHistory ;
3040import org .parosproxy .paros .model .HistoryReference ;
41+ import org .parosproxy .paros .model .Model ;
42+ import org .parosproxy .paros .model .Session ;
3143import org .parosproxy .paros .network .HttpMalformedHeaderException ;
3244import org .parosproxy .paros .network .HttpMessage ;
3345import org .zaproxy .zap .authentication .AuthenticationHelper ;
3446
3547/** A very thin layer on top of the History functionality, to make testing easier. */
36- public class HistoryProvider {
48+ public class HistoryProvider implements SessionChangedListener {
3749
38- private static final int MAX_NUM_RECORDS_TO_CHECK = 200 ;
50+ protected static final int MAX_NUM_RECORDS_TO_CHECK = 200 ;
3951
4052 private static final Logger LOGGER = LogManager .getLogger (HistoryProvider .class );
4153
54+ private static final String QUERY_SESS_MGMT_TOKEN_MSG_IDS =
55+ """
56+ SELECT HISTORYID FROM HISTORY
57+ WHERE HISTORYID BETWEEN ? AND ?
58+ AND (
59+ POSITION(? IN RESHEADER) > 0
60+ OR POSITION(? IN RESBODY) > 0
61+ OR POSITION(? IN REQHEADER) > 0
62+ OR POSITION(? IN REQHEADER) > 0 -- URLEncoded
63+ OR POSITION(? IN RESBODY) > 0 -- JSONEscaped
64+ )
65+ ORDER BY HISTORYID DESC
66+ """ ;
67+
68+ private ParosDatabaseServer pds ;
69+ private boolean warnedNonParosDb ;
70+ private boolean server ;
71+
4272 private ExtensionHistory extHist ;
4373
4474 private ExtensionHistory getExtHistory () {
@@ -61,21 +91,77 @@ public HttpMessage getHttpMessage(int historyId)
6191 return null ;
6292 }
6393
94+ /**
95+ * The query is ordered DESCending so the List and subsequent processing should be newest
96+ * message first.
97+ */
98+ List <Integer > getMessageIds (int first , int last , String value ) {
99+ if (!server ) {
100+ getParosDataBaseServer ();
101+ }
102+ if (pds == null ) {
103+ return null ;
104+ }
105+
106+ Connection conn = null ;
107+ try {
108+ conn = pds .getSingletonConnection ();
109+ } catch (SQLException | NullPointerException e ) {
110+ LOGGER .warn ("Failed to get DB connection." , e );
111+ return List .of ();
112+ }
113+
114+ PreparedStatement psGetHistory = null ;
115+ try {
116+ if (psGetHistory == null || psGetHistory .isClosed ()) {
117+ psGetHistory = conn .prepareStatement (QUERY_SESS_MGMT_TOKEN_MSG_IDS );
118+ }
119+ psGetHistory .setInt (1 , first );
120+ psGetHistory .setInt (2 , last );
121+ psGetHistory .setString (3 , value );
122+ psGetHistory .setBytes (4 , value .getBytes (StandardCharsets .UTF_8 ));
123+ psGetHistory .setString (5 , value );
124+ psGetHistory .setString (6 , URLEncoder .encode (value , StandardCharsets .UTF_8 ));
125+ psGetHistory .setBytes (
126+ 7 , StringEscapeUtils .escapeJson (value ).getBytes (StandardCharsets .UTF_8 ));
127+ } catch (SQLException e ) {
128+ LOGGER .warn ("Failed to prepare query." , e );
129+ return List .of ();
130+ }
131+
132+ List <Integer > msgIds = new ArrayList <>();
133+ try (ResultSet rs = psGetHistory .executeQuery ()) {
134+ while (rs .next ()) {
135+ msgIds .add (rs .getInt ("HISTORYID" ));
136+ }
137+ } catch (SQLException e ) {
138+ LOGGER .warn ("Failed to process result set. {}" , e .getMessage ());
139+ } finally {
140+ try {
141+ psGetHistory .close ();
142+ } catch (SQLException e ) {
143+ // Nothing to do
144+ }
145+ }
146+ LOGGER .debug ("Found: {} candidate messages for {}" , msgIds .size (), value );
147+ return msgIds ;
148+ }
149+
64150 public int getLastHistoryId () {
65151 return getExtHistory ().getLastHistoryId ();
66152 }
67153
68154 public SessionManagementRequestDetails findSessionTokenSource (String token , int firstId ) {
69155 int lastId = getLastHistoryId ();
70156 if (firstId == -1 ) {
71- firstId = Math .max (0 , lastId - MAX_NUM_RECORDS_TO_CHECK );
157+ firstId = Math .max (1 , lastId - MAX_NUM_RECORDS_TO_CHECK );
72158 }
73159
74160 LOGGER .debug ("Searching for session token from {} down to {} " , lastId , firstId );
75161
76- for (int i = lastId ; i >= firstId ; i -- ) {
162+ for (int id : getMessageIds ( firstId , lastId , token ) ) {
77163 try {
78- HttpMessage msg = getHttpMessage (i );
164+ HttpMessage msg = getHttpMessage (id );
79165 if (msg == null ) {
80166 continue ;
81167 }
@@ -99,4 +185,42 @@ public SessionManagementRequestDetails findSessionTokenSource(String token, int
99185 }
100186 return null ;
101187 }
188+
189+ private ParosDatabaseServer getParosDataBaseServer () {
190+ if (Model .getSingleton ().getDb () == null ) {
191+ return null ; // This should only happen in unit tests
192+ }
193+ if (Model .getSingleton ().getDb ().getDatabaseServer () instanceof ParosDatabaseServer pdbs ) {
194+ pds = pdbs ;
195+ server = true ;
196+ } else {
197+ if (pds == null && !warnedNonParosDb ) {
198+ LOGGER .warn ("Unsupported Database Server." );
199+ warnedNonParosDb = true ;
200+ }
201+ }
202+ return pds ;
203+ }
204+
205+ @ Override
206+ public void sessionChanged (Session session ) {
207+ pds = null ;
208+ server = false ;
209+ warnedNonParosDb = false ;
210+ }
211+
212+ @ Override
213+ public void sessionAboutToChange (Session session ) {
214+ // Nothing to do
215+ }
216+
217+ @ Override
218+ public void sessionScopeChanged (Session session ) {
219+ // Nothing to do
220+ }
221+
222+ @ Override
223+ public void sessionModeChanged (Mode mode ) {
224+ // Nothing to do
225+ }
102226}
0 commit comments