Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ abstract class BoxInstance(

is NaiveBean -> {
initPlugin("naive-plugin")
pluginConfigs[port] = profile.type to bean.buildNaiveConfig(port)
val creds = config.localProxyCredentials[port]
pluginConfigs[port] = profile.type to
bean.buildNaiveConfig(port, creds?.first, creds?.second)
}

is HysteriaBean -> {
Expand Down
27 changes: 26 additions & 1 deletion app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import io.nekohasekai.sagernet.fmt.tuic.TuicBean
import io.nekohasekai.sagernet.fmt.tuic.buildSingBoxOutboundTuicBean
import io.nekohasekai.sagernet.fmt.juicity.JuicityBean
import io.nekohasekai.sagernet.fmt.juicity.buildSingBoxOutboundJuicityBean
import io.nekohasekai.sagernet.fmt.naive.NaiveBean
import java.util.UUID
import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean
import io.nekohasekai.sagernet.fmt.v2ray.buildSingBoxOutboundStandardV2RayBean
import io.nekohasekai.sagernet.fmt.shadowsocksr.ShadowsocksRBean
Expand Down Expand Up @@ -69,6 +71,12 @@ class ConfigBuildResult(
var trafficMap: Map<String, List<ProxyEntity>>,
var profileTagMap: Map<Long, String>,
val selectorGroupId: Long,
// Per-port credentials for authenticated local SOCKS loopbacks of external
// plugins (e.g. naive). Android does not isolate 127.0.0.1 per app, so an
// unauthenticated plugin SOCKS listener could be reached by any local app and
// leak the egress IP (issue #1166). The plugin listens with these creds and the
// sing-box socks outbound dials with them.
val localProxyCredentials: Map<Int, Pair<String, String>> = emptyMap(),
) {
data class IndexEntity(var chain: LinkedHashMap<Int, ProxyEntity>)
}
Expand All @@ -95,6 +103,8 @@ fun buildConfig(
val trafficMap = HashMap<String, List<ProxyEntity>>()
val tagMap = HashMap<Long, String>()
val globalOutbounds = HashMap<Long, String>()
// Per-port credentials for authenticated external-plugin SOCKS loopbacks (#1166).
val localProxyCredentials = HashMap<Int, Pair<String, String>>()
val readableNames = mutableSetOf(TAG_DIRECT, TAG_BYPASS, TAG_BLOCK, TAG_FRAGMENT, TAG_MIXED, TAG_PROXY)
val group = SagerDatabase.groupDao.getById(proxy.groupId)

Expand Down Expand Up @@ -377,6 +387,20 @@ fun buildConfig(
type = "socks"
server = LOCALHOST
server_port = localPort
// Authenticate the local SOCKS loopback for plugins that support
// it (naive), so other apps on the device can't use this open
// 127.0.0.1 port to leak the egress IP (#1166). The plugin is
// configured to listen with the same generated credentials.
// Skip for export: the exported naive config (ProxyEntity.
// buildNaiveConfig without creds) would otherwise mismatch and
// produce a broken standalone config.
if (bean is NaiveBean && !forExport) {
val user = "neko"
val pass = UUID.randomUUID().toString().replace("-", "")
localProxyCredentials[localPort] = user to pass
username = user
password = pass
}
}
} else {
// internal outbound
Expand Down Expand Up @@ -941,7 +965,8 @@ fun buildConfig(
proxy.id,
trafficMap,
tagMap,
if (buildSelector) group.id else -1L
if (buildSelector) group.id else -1L,
localProxyCredentials,
)
}

Expand Down
14 changes: 12 additions & 2 deletions app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveFmt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fun NaiveBean.toUri(proxyOnly: Boolean = false): String {
return builder.toLink(if (proxyOnly) proto else "naive+$proto", false)
}

fun NaiveBean.buildNaiveConfig(port: Int): String {
fun NaiveBean.buildNaiveConfig(port: Int, listenUsername: String? = null, listenPassword: String? = null): String {
return JSONObject().apply {
// process ipv6
finalAddress = finalAddress.wrapIPV6Host()
Expand All @@ -75,7 +75,17 @@ fun NaiveBean.buildNaiveConfig(port: Int): String {
}
}

put("listen", "socks://$LOCALHOST:$port")
// Authenticate the local SOCKS listener so other apps on the device cannot use
// this loopback port as an open relay and leak the egress IP (#1166). The
// sing-box socks outbound dials with the same credentials.
if (!listenUsername.isNullOrBlank() && !listenPassword.isNullOrBlank()) {
put(
"listen",
"socks://${listenUsername.urlSafe()}:${listenPassword.urlSafe()}@$LOCALHOST:$port"
)
} else {
put("listen", "socks://$LOCALHOST:$port")
}
put("proxy", toUri(true))
if (extraHeaders.isNotBlank()) {
put("extra-headers", extraHeaders.split("\n").joinToString("\r\n"))
Expand Down
Loading