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