VYPR
Moderate severityNVD Advisory· Published Feb 27, 2025· Updated Mar 3, 2025

CVE-2025-22952

CVE-2025-22952

Description

elestio memos v0.23.0 is vulnerable to Server-Side Request Forgery (SSRF) due to insufficient validation of user-supplied URLs, which can be exploited to perform SSRF attacks.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Memos v0.23.0 fails to validate user-supplied URLs, allowing SSRF attacks against internal networks and services.

The vulnerability [CVE-2025-22952] affects elestio memos v0.23.0 and stems from insufficient validation of user-supplied URLs in the application's web link preview and metadata retrieval functionalities. The root cause is that the system does not properly restrict which IP addresses or protocols can be contacted when processing a user-provided URL, enabling attackers to bypass intended access controls [1][4].

A remote attacker can exploit this SSRF flaw by sending crafted HTTP requests that include internal IP addresses (such as 127.0.0.1 or 172.17.0.1) or other non-standard protocols to the affected application. No special privileges are required beyond the ability to submit a URL for preview. The application will then fetch the target URL, allowing the attacker to probe internal services and ports that are not directly accessible from the external network [2][4].

The impact is significant: an attacker can scan internal network hosts, access sensitive endpoints (such as cloud metadata services or internal admin panels), and potentially enumerate or interact with weak internal services. The vulnerability enables the attacker to effectively pivot from the application server into the internal network, compromising confidentiality and integrity of internal resources [1][4].

Mitigation is available via pull requests that add validation and block internal network URLs. Specifically, PR #4421 introduces checks to prevent previewing internal network web pages, and PR #4428 addresses redirect attacks in the GetLinkMetadata API [1][3]. Users are strongly advised to update to the latest patched version or apply the proposed fixes to their deployment.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/usememos/memosGo
<= 0.24.0

Affected products

1
  • elestio/memosdescription

Patches

2
f17774cb3b96

feat: prevent attackers from exploiting redirect attack GetLinkMetadata API (#4428)

https://github.com/usememos/memosMHZFeb 21, 2025via ghsa
2 files changed · +58 8
  • plugin/httpgetter/html_meta.go+38 8 modified
    @@ -1,16 +1,30 @@
     package httpgetter
     
     import (
    -	"errors"
     	"io"
     	"net"
     	"net/http"
     	"net/url"
     
    +	"github.com/pkg/errors"
     	"golang.org/x/net/html"
     	"golang.org/x/net/html/atom"
     )
     
    +var ErrInternalIP = errors.New("internal IP addresses are not allowed")
    +
    +var httpClient = &http.Client{
    +	CheckRedirect: func(req *http.Request, via []*http.Request) error {
    +		if err := validateURL(req.URL.String()); err != nil {
    +			return errors.Wrap(err, "redirect to internal IP")
    +		}
    +		if len(via) >= 10 {
    +			return errors.New("too many redirects")
    +		}
    +		return nil
    +	},
    +}
    +
     type HTMLMeta struct {
     	Title       string `json:"title"`
     	Description string `json:"description"`
    @@ -22,7 +36,7 @@ func GetHTMLMeta(urlStr string) (*HTMLMeta, error) {
     		return nil, err
     	}
     
    -	response, err := http.Get(urlStr)
    +	response, err := httpClient.Get(urlStr)
     	if err != nil {
     		return nil, err
     	}
    @@ -110,12 +124,28 @@ func validateURL(urlStr string) error {
     		return errors.New("only http/https protocols are allowed")
     	}
     
    -	if host := u.Hostname(); host != "" {
    -		ip := net.ParseIP(host)
    -		if ip != nil {
    -			if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
    -				return errors.New("internal IP addresses are not allowed")
    -			}
    +	host := u.Hostname()
    +	if host == "" {
    +		return errors.New("empty hostname")
    +	}
    +
    +	// check if the hostname is an IP
    +	if ip := net.ParseIP(host); ip != nil {
    +		if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
    +			return errors.Wrap(ErrInternalIP, ip.String())
    +		}
    +		return nil
    +	}
    +
    +	// check if it's a hostname, resolve it and check all returned IPs
    +	ips, err := net.LookupIP(host)
    +	if err != nil {
    +		return errors.Errorf("failed to resolve hostname: %v", err)
    +	}
    +
    +	for _, ip := range ips {
    +		if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
    +			return errors.Wrapf(ErrInternalIP, "host=%s, ip=%s", host, ip.String())
     		}
     	}
     
    
  • plugin/httpgetter/html_meta_test.go+20 0 modified
    @@ -1,6 +1,8 @@
     package httpgetter
     
     import (
    +	"errors"
    +	"strings"
     	"testing"
     
     	"github.com/stretchr/testify/require"
    @@ -17,3 +19,21 @@ func TestGetHTMLMeta(t *testing.T) {
     		require.Equal(t, test.htmlMeta, *metadata)
     	}
     }
    +
    +func TestGetHTMLMetaForInternal(t *testing.T) {
    +	// test for internal IP
    +	if _, err := GetHTMLMeta("http://192.168.0.1"); !errors.Is(err, ErrInternalIP) {
    +		t.Errorf("Expected error for internal IP, got %v", err)
    +	}
    +
    +	// test for resolved internal IP
    +	if _, err := GetHTMLMeta("http://localhost"); !errors.Is(err, ErrInternalIP) {
    +		t.Errorf("Expected error for resolved internal IP, got %v", err)
    +	}
    +
    +	// test for redirected internal IP
    +	// 49.232.126.226:1110 will redirects to 127.0.0.1
    +	if _, err := GetHTMLMeta("http://49.232.126.226:1110"); !(errors.Is(err, ErrInternalIP) && strings.Contains(err.Error(), "redirect")) {
    +		t.Errorf("Expected error for redirected internal IP, got %v", err)
    +	}
    +}
    
f8c973c93874

fix: prevent previewing internal network web pages. (#4421)

https://github.com/usememos/memosMHZFeb 19, 2025via ghsa
1 file changed · +26 1
  • plugin/httpgetter/html_meta.go+26 1 modified
    @@ -3,6 +3,7 @@ package httpgetter
     import (
     	"errors"
     	"io"
    +	"net"
     	"net/http"
     	"net/url"
     
    @@ -17,7 +18,7 @@ type HTMLMeta struct {
     }
     
     func GetHTMLMeta(urlStr string) (*HTMLMeta, error) {
    -	if _, err := url.Parse(urlStr); err != nil {
    +	if err := validateURL(urlStr); err != nil {
     		return nil, err
     	}
     
    @@ -35,6 +36,8 @@ func GetHTMLMeta(urlStr string) (*HTMLMeta, error) {
     		return nil, errors.New("not a HTML page")
     	}
     
    +	// TODO: limit the size of the response body
    +
     	htmlMeta := extractHTMLMeta(response.Body)
     	return htmlMeta, nil
     }
    @@ -96,3 +99,25 @@ func extractMetaProperty(token html.Token, prop string) (content string, ok bool
     	}
     	return content, ok
     }
    +
    +func validateURL(urlStr string) error {
    +	u, err := url.Parse(urlStr)
    +	if err != nil {
    +		return errors.New("invalid URL format")
    +	}
    +
    +	if u.Scheme != "http" && u.Scheme != "https" {
    +		return errors.New("only http/https protocols are allowed")
    +	}
    +
    +	if host := u.Hostname(); host != "" {
    +		ip := net.ParseIP(host)
    +		if ip != nil {
    +			if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
    +				return errors.New("internal IP addresses are not allowed")
    +			}
    +		}
    +	}
    +
    +	return nil
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

1