在对微信小程序进行安全测试时,我们常遇到一个现象:明明某企业有小程序,却无法通过微信搜索找到它。这并非小程序不存在,而是由于以下原因被“隐藏”:
- 小程序尚在审核中;开发者关闭了“可被搜索”选项;
- 内容涉及敏感领域,被平台限制收录;
- 仅为内部员工或特定用户开放。
这类未公开、不可搜索但实际运行的小程序,我们称之为 “影子小程序”。
AppID 是每个微信小程序的唯一身份标识,如同“身份证号”。对于影子小程序而言,即便其在微信搜索中不可见、未对外公开,只要处于运行状态,就必然拥有一个有效的 AppID。
通过 AppID,测试人员可以绕过搜索限制,直接定位到影子小程序的入口,进而发现其可能暴露的管理后台、调试接口等安全隐患。因此,AppID是发现和评估影子小程序风险的关键线索。
每一个小程序都有自己的appid,通过小程序前端文件夹名称、无影、或者等小程序反编译平台、burpsuite抓包,都能看到该小程序的appid。
当我们获取到一个小程序 appid 后,如何访问该 appid 对应的小程序呢?
我们可以直接搭建一个小程序来进行跳转:
注册账号
先去注册一个自己的小程序:mp.weixin.qq.com/wxopen/ware…
这里正常注册,小程序类目填 其他,主体类型填 个人 即可。
注册好了之后登陆进来小程序平台,填写小程序信息和小程序类目。
正常按照要求填就行了,类目哪里我填的是 工具 > 信息查询工具, 其实填啥无所谓,这个小程序不需要上线的,所以也就不需要备案。
然后点击开发管理,拿到你自己的 AppID 即可。
然后去下载一个微信开发者工具,下载自己平台的即可,我是 MAC 这里就下载 MAC 了。 developers.weixin.qq.com/miniprogram…
然后打开微信开发者工具,将自己的 AppID 填进去,注意是你自己的。然后注意点不使用云服务。
然后接下来我们只需要编写三个文件的代码即可,直接复制我下面的代码就行了。
index.scss:
// 1. 定义变量,方便统一管理颜色和尺寸
$color-primary: #667eea;
$color-primary-end: #764ba2;
$color-bg: #f5f5f5;
$color-white: #fff;
$color-text-main: #333;
$color-text-regular: #666;
$color-text-placeholder: #999;
$color-error: #ff4d4f;
$color-disabled-bg-start: #e0e0e0;
$color-disabled-bg-end: #d0d0d0;
$border-radius-lg: 16rpx;
$border-radius-sm: 8rpx;
$spacing-unit: 40rpx;
// 2. 定义混入 (Mixin),复用通用样式
@mixin flex-center {
display: flex;
align-items: center;
}
@mixin button-reset {
&::after {
border: none;
}
}
// 3. 使用嵌套和变量重写样式
page {
background-color: $color-bg;
min-height: 100vh;
}
.container {
padding: $spacing-unit;
}
// 头部
.header {
text-align: center;
margin-bottom: 60rpx;
.title {
display: block;
font-size: 48rpx;
font-weight: bold;
color: $color-text-main;
margin-bottom: 20rpx;
}
.subtitle {
display: block;
font-size: 28rpx;
color: $color-text-placeholder;
}
}
// 表单
.form {
background-color: $color-white;
border-radius: $border-radius-lg;
padding: $spacing-unit;
margin-bottom: $spacing-unit;
.form-item {
margin-bottom: $spacing-unit;
&:last-child {
margin-bottom: 0;
}
}
.label {
@include flex-center;
margin-bottom: 20rpx;
font-size: 28rpx;
color: $color-text-main;
.required {
color: $color-error;
margin-right: 8rpx;
font-weight: bold;
}
.optional {
margin-left: 8rpx;
font-size: 24rpx;
color: $color-text-placeholder;
}
}
.input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
background-color: $color-bg;
border-radius: $border-radius-sm;
font-size: 28rpx;
box-sizing: border-box;
}
}
// 按钮组
.button-group {
display: flex;
flex-direction: column;
gap: 24rpx;
margin-bottom: $spacing-unit;
}
.jump-button {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background: linear-gradient(135deg, $color-primary 0%, $color-primary-end 100%);
color: $color-white;
border-radius: $border-radius-lg;
font-size: 32rpx;
font-weight: bold;
border: none;
box-shadow: 0 8rpx 24rpx rgba($color-primary, 0.3);
transition: all 0.3s ease;
@include button-reset;
&[disabled] {
background: linear-gradient(135deg, $color-disabled-bg-start 0%, $color-disabled-bg-end 100%);
color: $color-text-placeholder;
box-shadow: none;
}
}
.clear-button {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background-color: $color-white;
color: $color-text-regular;
border: none;
border-radius: $border-radius-lg;
font-size: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
@include button-reset;
}
// 提示信息
.tips {
background-color: $color-white;
border-radius: $border-radius-lg;
padding: $spacing-unit;
.tips-title {
font-size: 28rpx;
font-weight: bold;
color: $color-text-main;
margin-bottom: 20rpx;
}
.tips-item {
font-size: 26rpx;
color: $color-text-regular;
line-height: 44rpx;
margin-bottom: 12rpx;
}
}
index.ts:
// index.ts
Component({
data: {
appId: '',
path: '',
extraData: '',
},
methods: {
// 输入 AppID
onAppIdInput(e: any) {
this.setData({
appId: e.detail.value.trim()
})
},
// 输入页面路径
onPathInput(e: any) {
this.setData({
path: e.detail.value.trim()
})
},
// 输入额外参数
onExtraDataInput(e: any) {
this.setData({
extraData: e.detail.value.trim()
})
},
// 跳转到其他小程序
navigateToMiniProgram() {
const { appId, path, extraData } = this.data
if (!appId) {
wx.showToast({
title: '请输入目标小程序 AppID',
icon: 'none'
})
return
}
// 构建完整路径
let fullPath = path || ''
if (fullPath && extraData) {
fullPath += (fullPath.includes('?') ? '&' : '?') + extraData
}
wx.navigateToMiniProgram({
appId: appId,
path: fullPath,
success: () => {
console.log('跳转成功')
},
fail: (err) => {
console.error('跳转失败:', err)
let errorMsg = '跳转失败'
if (err.errMsg.includes('navigateToMiniProgram:fail invalid appid')) {
errorMsg = 'AppID 无效或目标小程序不存在'
} else if (err.errMsg.includes('navigateToMiniProgram:fail cancel')) {
errorMsg = '用户取消跳转'
} else if (err.errMsg.includes('path')) {
errorMsg = '页面路径错误'
}
wx.showModal({
title: '跳转失败',
content: errorMsg + '\n\n请确保:\n1. 目标小程序 AppID 正确\n2. 目标小程序已发布\n3. 页面路径正确(如有填写)',
showCancel: false
})
}
})
},
// 清空表单
clearForm() {
this.setData({
appId: '',
path: '',
extraData: ''
})
}
}
})
index.wxml:
<!--index.wxml-->
<view class="container">
<view class="header">
<text class="title">小程序跳转工具</text>
<text class="subtitle">输入目标小程序信息进行跳转</text>
</view>
<view class="form">
<view class="form-item">
<view class="label">
<text class="required">*</text>
<text>目标小程序 AppID</text>
</view>
<input
class="input"
placeholder="请输入目标小程序的 AppID"
value="{{appId}}"
bindinput="onAppIdInput"
/>
</view>
<view class="form-item">
<view class="label">
<text>目标页面路径</text>
<text class="optional">(可选)</text>
</view>
<input
class="input"
placeholder="例如: pages/index/index"
value="{{path}}"
bindinput="onPathInput"
/>
</view>
<view class="form-item">
<view class="label">
<text>页面参数</text>
<text class="optional">(可选)</text>
</view>
<input
class="input"
placeholder="例如: id=123&name=test"
value="{{extraData}}"
bindinput="onExtraDataInput"
/>
</view>
</view>
<view class="button-group">
<button class="jump-button" bindtap="navigateToMiniProgram" disabled="{{!appId}}">
跳转小程序
</button>
<button class="clear-button" bindtap="clearForm">
清空
</button>
</view>
<view class="tips">
<view class="tips-title">使用说明:</view>
<view class="tips-item">1. AppID 为必填项,填写要跳转的目标小程序 AppID</view>
<view class="tips-item">2. 页面路径为可选,如: pages/index/index,不填则跳转到目标小程序首页</view>
<view class="tips-item">3. 页面参数为可选,格式: key1=value1&key2=value2</view>
<view class="tips-item">4. 从2020年4月起,跳转其他小程序无需配置白名单</view>
<view class="tips-item">5. 需要用户点击按钮触发跳转,会弹窗询问用户是否跳转</view>
</view>
</view>
然后还需要改一下 app.json,只需要改里面一小段代码即可,这个文件里的其他代码都不动:
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "小程序跳转工具",
"navigationBarBackgroundColor": "#ffffff",
"navigationStyle": "custom"
},
编写完代码,保存代码,然后点击预览就会出现一个二维码,手机扫码就可以使用这个小程序了: