
Notably at least two instances were not properly guarded from path traversal attack before and are only now fixed by using SafeZip: - frontend installation did never check for malicious paths. But given a malicious froontend could already, e.g. steal all user tokens even without this, in the real world admins should only use frontends from trusted sources and the practical implications are minimal - the emoji pack update/upload API taking a ZIP file did not protect against path traversal. While atm only admins can use these emoji endpoints, emoji packs are typically considered "harmless" and used without prior verification from various sources. Thus this appears more concerning.
102 lines
2.7 KiB
Elixir
102 lines
2.7 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
defmodule Pleroma.Frontend do
|
|
alias Pleroma.Config
|
|
|
|
require Logger
|
|
|
|
def install(name, opts \\ []) do
|
|
frontend_info = %{
|
|
"ref" => opts[:ref],
|
|
"build_url" => opts[:build_url],
|
|
"build_dir" => opts[:build_dir]
|
|
}
|
|
|
|
frontend_info =
|
|
[:frontends, :available, name]
|
|
|> Config.get(%{})
|
|
|> Map.merge(frontend_info, fn _key, config, cmd ->
|
|
# This only overrides things that are actually set
|
|
cmd || config
|
|
end)
|
|
|
|
ref = frontend_info["ref"]
|
|
|
|
unless ref do
|
|
raise "No ref given or configured"
|
|
end
|
|
|
|
dest = Path.join([dir(), name, ref])
|
|
|
|
label = "#{name} (#{ref})"
|
|
tmp_dir = Path.join(dir(), "tmp")
|
|
IO.puts("Downloading #{label}...")
|
|
|
|
with {_, :ok} <-
|
|
{:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, opts[:file])},
|
|
IO.puts("Installing #{label} to #{dest}"),
|
|
:ok <- install_frontend(frontend_info, tmp_dir, dest) do
|
|
File.rm_rf!(tmp_dir)
|
|
IO.puts("Frontend #{label} installed to #{dest}")
|
|
else
|
|
{:download_or_unzip, _} ->
|
|
IO.puts("Could not download or unzip the frontend")
|
|
{:error, "Could not download or unzip the frontend"}
|
|
|
|
_e ->
|
|
IO.puts("Could not install the frontend")
|
|
{:error, "Could not install the frontend"}
|
|
end
|
|
end
|
|
|
|
def dir(opts \\ []) do
|
|
if is_nil(opts[:static_dir]) do
|
|
Pleroma.Config.get!([:instance, :static_dir])
|
|
else
|
|
opts[:static_dir]
|
|
end
|
|
|> Path.join("frontends")
|
|
end
|
|
|
|
defp download_or_unzip(frontend_info, temp_dir, nil),
|
|
do: download_build(frontend_info, temp_dir)
|
|
|
|
defp download_or_unzip(_frontend_info, temp_dir, file) do
|
|
with {:ok, zip} <- File.read(Path.expand(file)) do
|
|
unzip(zip, temp_dir)
|
|
end
|
|
end
|
|
|
|
def unzip(zip, dest) do
|
|
File.rm_rf!(dest)
|
|
File.mkdir_p!(dest)
|
|
|
|
case Pleroma.SafeZip.unzip_data(zip, dest) do
|
|
{:ok, _} -> :ok
|
|
error -> error
|
|
end
|
|
end
|
|
|
|
defp download_build(frontend_info, dest) do
|
|
Logger.info("Downloading pre-built bundle for #{frontend_info["name"]}")
|
|
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
|
|
|
with {:ok, %{status: 200, body: zip_body}} <-
|
|
Pleroma.HTTP.get(url, [], receive_timeout: 120_000) do
|
|
unzip(zip_body, dest)
|
|
else
|
|
{:error, e} -> {:error, e}
|
|
e -> {:error, e}
|
|
end
|
|
end
|
|
|
|
defp install_frontend(frontend_info, source, dest) do
|
|
from = frontend_info["build_dir"] || "dist"
|
|
File.rm_rf!(dest)
|
|
File.mkdir_p!(dest)
|
|
File.cp_r!(Path.join([source, from]), dest)
|
|
:ok
|
|
end
|
|
end
|