1+
2+
3+ resolve . df = false ;
4+
5+ function resolve ( url , docroot = '.' ) {
6+ resolve . df && console . log ( '=== resolve v2.0 ===' ) ;
7+ resolve . df && console . log ( 'url :' , url ) ;
8+ resolve . df && console . log ( 'docroot :' , docroot ) ;
9+ var err ;
10+ try {
11+
12+ url = decodeURI ( url ) ;
13+
14+ } //try
15+ catch ( err2 ) {
16+
17+ err = err2 ;
18+
19+ } //catch
20+ if ( err ) {
21+ resolve . df && console . error ( err ) ;
22+ return false ;
23+ }
24+
25+ url = url . slice ( 1 ) ;
26+ resolve . df && console . log ( 'url :' , url ) ;
27+ var root = path . resolve ( docroot ) ;
28+ root += path . sep ;
29+ resolve . df && console . log ( 'root :' , root ) ;
30+ var abs = path . resolve ( docroot , url ) ;
31+ resolve . df && console . log ( 'abs :' , abs ) ;
32+
33+ if ( ! abs . startsWith ( root ) ) {
34+ resolve . df && console . log ( 'fail' ) ;
35+ return false ;
36+ }
37+
38+ if ( url . endsWith ( '/' ) ) {
39+ abs += '/' ;
40+ }
41+ resolve . df && console . log ( 'ok' , abs ) ;
42+ return abs ;
43+
44+ } //resolve
45+
46+
47+
48+
49+ var path = require ( 'path' ) ;
50+ var test = setup ( ) ;
51+
52+
53+ test . tests . forEach ( ( item , i ) => {
54+
55+ var abs = resolve ( item . url , test . docroot ) ;
56+
57+ console . log ( ) ;
58+ console . log ( i , item . note ) ;
59+ console . log ( 'value :' , item . url ) ;
60+ console . log ( 'expected :' , item . expected ) ;
61+ console . log ( 'result : ' , abs ) ;
62+ console . log ( ) ;
63+
64+ } ) ;
65+
66+
67+
68+ function setup ( ) {
69+
70+ return {
71+
72+ "docroot" : "/var/www" ,
73+ "tests" : [
74+ {
75+ "url" : "/index.html" ,
76+ "expected" : "/var/www/index.html" ,
77+ "note" : "Basic file"
78+ } ,
79+ {
80+ "url" : "/css/style.css" ,
81+ "expected" : "/var/www/css/style.css" ,
82+ "note" : "Nested file"
83+ } ,
84+ {
85+ "url" : "/images/" ,
86+ "expected" : "/var/www/images/" ,
87+ "note" : "Directory with trailing slash"
88+ } ,
89+ {
90+ "url" : "/folder/sub/file.txt" ,
91+ "expected" : "/var/www/folder/sub/file.txt" ,
92+ "note" : "Deep nested file"
93+ } ,
94+
95+ /* Traversal attempts */
96+ {
97+ "url" : "/../secret.txt" ,
98+ "expected" : false ,
99+ "note" : "Simple traversal"
100+ } ,
101+ {
102+ "url" : "/../../etc/passwd" ,
103+ "expected" : false ,
104+ "note" : "Traversal to root"
105+ } ,
106+ {
107+ "url" : "/foo/../../secret.txt" ,
108+ "expected" : false ,
109+ "note" : "Traversal inside nested path"
110+ } ,
111+ {
112+ "url" : "/.." ,
113+ "expected" : false ,
114+ "note" : "Parent directory"
115+ } ,
116+ {
117+ "url" : "/../" ,
118+ "expected" : false ,
119+ "note" : "Parent directory with slash"
120+ } ,
121+
122+ /* Encoded traversal */
123+ {
124+ "url" : "/%2e%2e/secret.txt" ,
125+ "expected" : false ,
126+ "note" : "Encoded .."
127+ } ,
128+ {
129+ "url" : "/foo/%2e%2e/%2e%2e/passwd" ,
130+ "expected" : false ,
131+ "note" : "Double encoded traversal"
132+ } ,
133+ {
134+ "url" : "/%2e%2e/%2e%2e/etc/passwd" ,
135+ "expected" : false ,
136+ "note" : "Encoded traversal to root"
137+ } ,
138+
139+ /* Double-encoded traversal */
140+ {
141+ "url" : "/%252e%252e/%252e%252e/etc/passwd" ,
142+ "expected" : "/var/www/%2e%2e/%2e%2e/etc/passwd" ,
143+ "note" : "Double-encoded stays inside docroot after single decode"
144+ } ,
145+
146+ /* Mixed slash styles */
147+ {
148+ "url" : "/..\\..\\Windows\\win.ini" ,
149+ "expected" : false ,
150+ "note" : "Windows backslash traversal"
151+ } ,
152+ {
153+ "url" : "/folder\\sub\\file.txt" ,
154+ "expected" : "/var/www/folder/sub/file.txt" ,
155+ "note" : "Windows slashes inside docroot"
156+ } ,
157+
158+ /* Absolute path attempts */
159+ {
160+ "url" : "/etc/passwd" ,
161+ "expected" : "/var/www/etc/passwd" ,
162+ "note" : "Absolute path becomes relative after slice"
163+ } ,
164+ {
165+ "url" : "C:\\Windows\\win.ini" ,
166+ "expected" : false ,
167+ "note" : "Windows absolute path attack"
168+ } ,
169+ {
170+ "url" : "/C:/Windows/win.ini" ,
171+ "expected" : false ,
172+ "note" : "Windows absolute path with leading slash"
173+ } ,
174+
175+ /* Malformed garbage */
176+ {
177+ "url" : "/....//....//etc/passwd" ,
178+ "expected" : false ,
179+ "note" : "Weird dot spam traversal"
180+ } ,
181+ {
182+ "url" : "/foo/%" ,
183+ "expected" : false ,
184+ "note" : "Malformed percent encoding"
185+ } ,
186+ {
187+ "url" : "/foo/%0" ,
188+ "expected" : false ,
189+ "note" : "Malformed percent encoding 2"
190+ } ,
191+
192+ /* Unicode weirdness */
193+ {
194+ "url" : "/föö/bar.txt" ,
195+ "expected" : "/var/www/föö/bar.txt" ,
196+ "note" : "Unicode filename"
197+ } ,
198+ {
199+ "url" : "/%C0%AFetc/passwd" ,
200+ "expected" : false ,
201+ "note" : "Overlong UTF-8 slash"
202+ } ,
203+
204+ /* Directory edge cases */
205+ {
206+ "url" : "/" ,
207+ "expected" : "/var/www/" ,
208+ "note" : "Root directory"
209+ } ,
210+ {
211+ "url" : "/./" ,
212+ "expected" : "/var/www/" ,
213+ "note" : "Dot directory"
214+ } ,
215+ {
216+ "url" : "/folder/./file.txt" ,
217+ "expected" : "/var/www/folder/file.txt" ,
218+ "note" : "Dot inside path"
219+ } ,
220+ {
221+ "url" : "/folder///sub///file.txt" ,
222+ "expected" : "/var/www/folder/sub/file.txt" ,
223+ "note" : "Multiple slashes"
224+ } ,
225+
226+ /* Ultra-weird attacker payloads */
227+ {
228+ "url" : "/..////..////etc/passwd" ,
229+ "expected" : false ,
230+ "note" : "Slash spam traversal"
231+ } ,
232+ {
233+ "url" : "/foo/%2e%2e%2f%2e%2e%2fetc/passwd" ,
234+ "expected" : false ,
235+ "note" : "Encoded slash traversal"
236+ } ,
237+ {
238+ "url" : "/foo/%F0%9F%92%A9/bar.txt" ,
239+ "expected" : "/var/www/foo/💩/bar.txt" ,
240+ "note" : "Emoji in path"
241+ }
242+ ]
243+ }
244+
245+ } //setup
246+
247+
248+
249+
250+
0 commit comments